def __getattr__(self, attribute: str) -> Any: """ Return given attribute value while handling deprecation. Parameters ---------- attribute Attribute name. Returns ------- :class:`object` Attribute value. Raises ------ AttributeError If the attribute is not defined. """ change = self._changes.get(attribute) if change is not None: if not isinstance(change, ObjectRemoved): usage_warning(str(change)) return (getattr(self._module, attribute) if isinstance( change, ObjectFutureRemove) else get_attribute(change[1])) else: raise AttributeError(str(change)) return getattr(self._module, attribute)
def __getattr__(self, attribute): """ Returns given attribute value while handling deprecation. Parameters ---------- attribute : unicode Attribute name. Returns ------- object Attribute value. Raises ------ AttributeError If the attribute is not defined. """ change = self._changes.get(attribute) if change is not None: if not isinstance(change, Removed): usage_warning(str(change)) return get_attribute(change[1]) else: raise AttributeError(str(change)) return getattr(self._module, attribute)
def handle_arguments_deprecation(changes, **kwargs): """ Handles arguments deprecation according to desired API changes mapping. Parameters ---------- changes : dict Dictionary of desired API changes. Other Parameters ---------------- \\**kwargs : dict, optional Keywords arguments to handle. Returns ------- dict Handled keywords arguments. Examples -------- >>> changes = { ... 'ArgumentRenamed': [[ ... 'argument_1_name', ... 'argument_1_new_name', ... ]], ... 'ArgumentFutureRename': [[ ... 'argument_2_name', ... 'argument_2_new_name', ... ]], ... 'ArgumentRemoved': ['argument_3_name'], ... 'ArgumentFutureRemove': ['argument_4_name'], ... } >>> handle_arguments_deprecation(changes, argument_1_name=True, ... argument_2_name=True, argument_4_name=True) ... # doctest: +SKIP {'argument_4_name': True, 'argument_1_new_name': True, \ 'argument_2_new_name': True} """ changes = build_API_changes(changes) for kwarg in kwargs.copy(): change = changes.get(kwarg) if change is None: continue if not isinstance(change, ArgumentRemoved): usage_warning(str(change)) if isinstance(change, ArgumentFutureRemove): continue else: kwargs[change[1]] = kwargs.pop(kwarg) else: raise ValueError(str(change)) return kwargs
def CCT_to_xy_Kang2002(CCT): """ Returns the *CIE xy* chromaticity coordinates from given correlated colour temperature :math:`T_{cp}` using *Kang et al. (2002)* method. Parameters ---------- CCT : numeric or array_like Correlated colour temperature :math:`T_{cp}`. Returns ------- ndarray *CIE xy* chromaticity coordinates. Raises ------ ValueError If the correlated colour temperature is not in appropriate domain. References ---------- :cite:`Kang2002a` Examples -------- >>> CCT_to_xy_Kang2002(6504.38938305) # doctest: +ELLIPSIS array([ 0.313426 ..., 0.3235959...]) """ CCT = as_float_array(CCT) if np.any(CCT[np.asarray(np.logical_or(CCT < 1667, CCT > 25000))]): usage_warning(('Correlated colour temperature must be in domain ' '[1667, 25000], unpredictable results may occur!')) CCT_3 = CCT**3 CCT_2 = CCT**2 x = np.where( CCT <= 4000, -0.2661239 * 10**9 / CCT_3 - 0.2343589 * 10**6 / CCT_2 + 0.8776956 * 10**3 / CCT + 0.179910, -3.0258469 * 10**9 / CCT_3 + 2.1070379 * 10**6 / CCT_2 + 0.2226347 * 10**3 / CCT + 0.24039, ) x_3 = x**3 x_2 = x**2 cnd_l = [CCT <= 2222, np.logical_and(CCT > 2222, CCT <= 4000), CCT > 4000] i = -1.1063814 * x_3 - 1.34811020 * x_2 + 2.18555832 * x - 0.20219683 j = -0.9549476 * x_3 - 1.37418593 * x_2 + 2.09137015 * x - 0.16748867 k = 3.0817580 * x_3 - 5.8733867 * x_2 + 3.75112997 * x - 0.37001483 y = np.select(cnd_l, [i, j, k]) xy = tstack([x, y]) return xy
def interpolator_args(self): # Docstrings are omitted for documentation purposes. usage_warning( str( ObjectRenamed('Signal.interpolator_args', 'Signal.interpolator_kwargs'))) return self.interpolator_kwargs
def extrapolator_args(self, value): # Docstrings are omitted for documentation purposes. usage_warning( str( ObjectRenamed('Signal.extrapolator_args', 'Signal.extrapolator_kwargs'))) self.extrapolator_kwargs = value
def cctf_decoding(value, function='sRGB', **kwargs): """ Decodes non-linear :math:`R'G'B'` values to linear :math:`RGB` values using given decoding colour component transfer function (Decoding CCTF). Parameters ---------- value : numeric or array_like Non-linear :math:`R'G'B'` values. function : unicode, optional {:attr:`colour.CCTF_DECODINGS`}, Computation function. Other Parameters ---------------- \\**kwargs : dict, optional Keywords arguments for the relevant decoding CCTF of the :attr:`colour.CCTF_DECODINGS` attribute collection. Warnings -------- For *ITU-R BT.2100*, only the electro-optical transfer functions (EOTFs / EOCFs) are exposed by this definition, please refer to the :func:`colour.oetf_inverse` definition for the inverse opto-electronic transfer functions (OETF / OECF). Returns ------- numeric or ndarray Linear :math:`RGB` values. Examples -------- >>> cctf_decoding(0.391006842619746, function='PLog', log_reference=400) ... # doctest: +ELLIPSIS 0.1... >>> cctf_decoding(0.182011532850008, function='ST 2084', L_p=1000) ... # doctest: +ELLIPSIS 0.1... >>> cctf_decoding( # doctest: +ELLIPSIS ... 0.461356129500442, function='ITU-R BT.1886') 0.1... """ if 'itu-r bt.2100' in function.lower(): usage_warning( 'With the "ITU-R BT.2100" method, only the electro-optical ' 'transfer functions (EOTFs / EOCFs) are exposed by this ' 'definition, please refer to the "colour.oetf_inverse" definition ' 'for the inverse opto-electronic transfer functions (OETF / OECF).' ) function = CCTF_DECODINGS[function] return function(value, **filter_kwargs(function, **kwargs))
def CCT_to_xy_Kang2002(CCT): """ Returns the *CIE XYZ* tristimulus values *xy* chromaticity coordinates from given correlated colour temperature :math:`T_{cp}` using *Kang et al. (2002)* method. Parameters ---------- CCT : numeric or array_like Correlated colour temperature :math:`T_{cp}`. Returns ------- ndarray *xy* chromaticity coordinates. Raises ------ ValueError If the correlated colour temperature is not in appropriate domain. References ---------- :cite:`Kang2002a` Examples -------- >>> CCT_to_xy_Kang2002(6504.38938305) # doctest: +ELLIPSIS array([ 0.313426 ..., 0.3235959...]) """ CCT = as_float_array(CCT) if np.any(CCT[np.asarray(np.logical_or(CCT < 1667, CCT > 25000))]): usage_warning(('Correlated colour temperature must be in domain ' '[1667, 25000], unpredictable results may occur!')) x = np.where( CCT <= 4000, -0.2661239 * 10 ** 9 / CCT ** 3 - 0.2343589 * 10 ** 6 / CCT ** 2 + 0.8776956 * 10 ** 3 / CCT + 0.179910, -3.0258469 * 10 ** 9 / CCT ** 3 + 2.1070379 * 10 ** 6 / CCT ** 2 + 0.2226347 * 10 ** 3 / CCT + 0.24039, ) cnd_l = [CCT <= 2222, np.logical_and(CCT > 2222, CCT <= 4000), CCT > 4000] i = -1.1063814 * x ** 3 - 1.34811020 * x ** 2 + 2.18555832 * x - 0.20219683 j = -0.9549476 * x ** 3 - 1.37418593 * x ** 2 + 2.09137015 * x - 0.16748867 k = 3.0817580 * x ** 3 - 5.8733867 * x ** 2 + 3.75112997 * x - 0.37001483 y = np.select(cnd_l, [i, j, k]) xy = tstack([x, y]) return xy
def encoding_cctf(value, function='sRGB', **kwargs): """ Encodes linear :math:`RGB` values to non linear :math:`R'G'B'` values using given encoding colour component transfer function (Encoding CCTF). Parameters ---------- value : numeric or array_like Linear :math:`RGB` values. function : unicode, optional {:attr:`colour.ENCODING_CCTFS`}, Computation function. Other Parameters ---------------- \\**kwargs : dict, optional Keywords arguments for the relevant encoding CCTF of the :attr:`colour.ENCODING_CCTFS` attribute collection. Warning ------- For *ITU-R BT.2100*, only the reverse electro-optical transfer functions (EOTFs / EOCFs) are exposed by this definition, please refer to the :func:`colour.oetf` definition for the opto-electronic transfer functions (OETF / OECF). Returns ------- numeric or ndarray Non linear :math:`R'G'B'` values. Examples -------- >>> encoding_cctf(0.18, function='PLog', log_reference=400) ... # doctest: +ELLIPSIS 0.3910068... >>> encoding_cctf(0.18, function='ST 2084', L_p=1000) ... # doctest: +ELLIPSIS 0.1820115... >>> encoding_cctf( # doctest: +ELLIPSIS ... 0.11699185725296059, function='ITU-R BT.1886') 0.4090077... """ if 'itu-r bt.2100' in function.lower(): usage_warning( 'For "ITU-R BT.2100", only the reverse electro-optical transfer ' 'functions (EOTFs / EOCFs) are exposed by this definition, please ' 'refer to the "colour.oetf" definition for the opto-electronic ' 'transfer functions (OETF / OECF).') function = ENCODING_CCTFS[function] return function(value, **filter_kwargs(function, **kwargs))
def decoding_cctf(value, function='Cineon', **kwargs): """ Decodes non-linear :math:`R'G'B'` values to linear :math:`RGB` values using given decoding colour component transfer function (Decoding CCTF). Parameters ---------- value : numeric or array_like Non-linear :math:`R'G'B'` values. function : unicode, optional {:attr:`colour.DECODING_CCTFS`}, Computation function. Other Parameters ---------------- \\**kwargs : dict, optional Keywords arguments for the relevant decoding CCTF of the :attr:`colour.DECODING_CCTFS` attribute collection. Warning ------- For *ITU-R BT.2100*, only the electro-optical transfer functions (EOTFs / EOCFs) are exposed by this definition, please refer to the :func:`colour.oetf_reverse` definition for the reverse opto-electronic transfer functions (OETF / OECF). Returns ------- numeric or ndarray Linear :math:`RGB` values. Examples -------- >>> decoding_cctf(0.391006842619746, function='PLog', log_reference=400) ... # doctest: +ELLIPSIS 0.1... >>> decoding_cctf(0.182011532850008, function='ST 2084', L_p=1000) ... # doctest: +ELLIPSIS 0.1... >>> decoding_cctf( # doctest: +ELLIPSIS ... 0.461356129500442, function='ITU-R BT.1886') 0.1... """ if 'itu-r bt.2100' in function.lower(): usage_warning( 'For "ITU-R BT.2100", only the electro-optical transfer functions ' '(EOTFs / EOCFs) are exposed by this definition, please refer to ' 'the "colour.oetf_reverse" definition for the reverse ' 'opto-electronic transfer functions (OETF / OECF).') function = DECODING_CCTFS[function] return function(value, **filter_kwargs(function, **kwargs))
def RGB_to_HEX(RGB): """ Converts from *RGB* colourspace to hexadecimal representation. Parameters ---------- RGB : array_like *RGB* colourspace array. Returns ------- unicode Hexadecimal representation. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``RGB`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ Examples -------- >>> RGB = np.array([0.66666667, 0.86666667, 1.00000000]) >>> # Doctests skip for Python 2.x compatibility. >>> RGB_to_HEX(RGB) # doctest: +SKIP '#aaddff' """ RGB = to_domain_1(RGB) if np.any(RGB < 0): usage_warning( '"RGB" array contains negative values, those will be clipped, ' 'unpredictable results may occur!') RGB = np.clip(RGB, 0, np.inf) if np.any(RGB > 1): usage_warning( '"RGB" array contains values over 1 and will be normalised, ' 'unpredictable results may occur!') RGB = eotf_inverse_sRGB(normalise_maximum(eotf_sRGB(RGB))) to_HEX = np.vectorize('{0:02x}'.format) HEX = to_HEX((RGB * 255).astype(np.uint8)).astype(object) HEX = np.asarray('#') + HEX[..., 0] + HEX[..., 1] + HEX[..., 2] return HEX
def CCT_to_xy_CIE_D(CCT): """ Returns the *CIE xy* chromaticity coordinates of a *CIE Illuminant D Series* from its correlated colour temperature :math:`T_{cp}`. Parameters ---------- CCT : numeric or array_like Correlated colour temperature :math:`T_{cp}`. Returns ------- ndarray *CIE xy* chromaticity coordinates. Raises ------ ValueError If the correlated colour temperature is not in appropriate domain. References ---------- :cite:`Wyszecki2000z` Examples -------- >>> CCT_to_xy_CIE_D(6504.38938305) # doctest: +ELLIPSIS array([ 0.3127077..., 0.3291128...]) """ CCT = as_float_array(CCT) if np.any(CCT[np.asarray(np.logical_or(CCT < 4000, CCT > 25000))]): usage_warning(('Correlated colour temperature must be in domain ' '[4000, 25000], unpredictable results may occur!')) CCT_3 = CCT**3 CCT_2 = CCT**2 x = np.where( CCT <= 7000, -4.607 * 10**9 / CCT_3 + 2.9678 * 10**6 / CCT_2 + 0.09911 * 10**3 / CCT + 0.244063, -2.0064 * 10**9 / CCT_3 + 1.9018 * 10**6 / CCT_2 + 0.24748 * 10**3 / CCT + 0.23704, ) y = daylight_locus_function(x) xy = tstack([x, y]) return xy
def lightness_Wyszecki1963(Y: FloatingOrArrayLike) -> FloatingOrNDArray: """ Return the *Lightness* :math:`W` of given *luminance* :math:`Y` using *Wyszecki (1963)* method. Parameters ---------- Y *Luminance* :math:`Y`. Returns ------- :class:`numpy.floating` or :class:`numpy.ndarray` *Lightness* :math:`W`. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``Y`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``W`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`Wyszecki1963b` Examples -------- >>> lightness_Wyszecki1963(12.19722535) # doctest: +ELLIPSIS 40.5475745... """ Y = to_domain_100(Y) if np.any(Y < 1) or np.any(Y > 98): usage_warning( '"W*" Lightness computation is only applicable for ' '1% < "Y" < 98%, unpredictable results may occur!' ) W = 25 * spow(Y, 1 / 3) - 17 return as_float(from_range_100(W))
def RGB_to_HEX(RGB: ArrayLike) -> StrOrNDArray: """ Convert from *RGB* colourspace to hexadecimal representation. Parameters ---------- RGB *RGB* colourspace array. Returns ------- :class:`str` or :class:`numpy.array` Hexadecimal representation. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``RGB`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ Examples -------- >>> RGB = np.array([0.66666667, 0.86666667, 1.00000000]) >>> RGB_to_HEX(RGB) '#aaddff' """ RGB = to_domain_1(RGB) if np.any(RGB < 0): usage_warning( '"RGB" array contains negative values, those will be clipped, ' "unpredictable results may occur!") RGB = as_float_array(np.clip(RGB, 0, np.inf)) if np.any(RGB > 1): usage_warning( '"RGB" array contains values over 1 and will be normalised, ' "unpredictable results may occur!") RGB = eotf_inverse_sRGB(normalise_maximum(eotf_sRGB(RGB))) to_HEX = np.vectorize("{:02x}".format) HEX = to_HEX(as_int_array(RGB * 255, dtype=np.uint8)).astype(object) HEX = np.asarray("#") + HEX[..., 0] + HEX[..., 1] + HEX[..., 2] return HEX
def lightness_Wyszecki1963(Y): """ Returns the *Lightness* :math:`W` of given *luminance* :math:`Y` using *Wyszecki (1963)* method. Parameters ---------- Y : numeric or array_like *luminance* :math:`Y`. Returns ------- numeric or array_like *Lightness* :math:`W`. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``Y`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``W`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`Wyszecki1963b` Examples -------- >>> lightness_Wyszecki1963(12.19722535) # doctest: +ELLIPSIS 40.5475745... """ Y = to_domain_100(Y) if np.any(Y < 1) or np.any(Y > 98): usage_warning('"W*" Lightness computation is only applicable for ' '1% < "Y" < 98%, unpredictable results may occur!') W = 25 * spow(Y, 1 / 3) - 17 return from_range_100(W)
def CCT_to_xy_CIE_D(CCT): """ Converts from the correlated colour temperature :math:`T_{cp}` of a *CIE Illuminant D Series* to the chromaticity of that *CIE Illuminant D Series* illuminant. Parameters ---------- CCT : numeric or array_like Correlated colour temperature :math:`T_{cp}`. Returns ------- ndarray *xy* chromaticity coordinates. Raises ------ ValueError If the correlated colour temperature is not in appropriate domain. References ---------- :cite:`Wyszecki2000z` Examples -------- >>> CCT_to_xy_CIE_D(6504.38938305) # doctest: +ELLIPSIS array([ 0.3127077..., 0.3291128...]) """ CCT = as_float_array(CCT) if np.any(CCT[np.asarray(np.logical_or(CCT < 4000, CCT > 25000))]): usage_warning(('Correlated colour temperature must be in domain ' '[4000, 25000], unpredictable results may occur!')) x = np.where( CCT <= 7000, -4.607 * 10 ** 9 / CCT ** 3 + 2.9678 * 10 ** 6 / CCT ** 2 + 0.09911 * 10 ** 3 / CCT + 0.244063, -2.0064 * 10 ** 9 / CCT ** 3 + 1.9018 * 10 ** 6 / CCT ** 2 + 0.24748 * 10 ** 3 / CCT + 0.23704, ) y = daylight_locus_function(x) xy = tstack([x, y]) return xy
def write_LUT_IridasCube(LUT, path, decimals=7): """ Writes given *LUT* to given *Iridas* *.cube* *LUT* file. Parameters ---------- LUT : LUT3x1D or LUT3d or LUTSequence :class:`LUT3x1D`, :class:`LUT3D` or :class:`LUTSequence` class instance to write at given path. path : unicode *LUT* path. decimals : int, optional Formatting decimals. Returns ------- bool Definition success. Warning ------- - If a :class:`LUTSequence` class instance is passed as ``LUT``, the first *LUT* in the *LUT* sequence will be used. References ---------- :cite:`AdobeSystems2013b` Examples -------- Writing a 3x1D *Iridas* *.cube* *LUT*: >>> from colour.algebra import spow >>> domain = np.array([[-0.1, -0.2, -0.4], [1.5, 3.0, 6.0]]) >>> LUT = LUT3x1D( ... spow(LUT3x1D.linear_table(16, domain), 1 / 2.2), ... 'My LUT', ... domain, ... comments=['A first comment.', 'A second comment.']) >>> write_LUT_IridasCube(LUT, 'My_LUT.cube') # doctest: +SKIP Writing a 3D *Iridas* *.cube* *LUT*: >>> domain = np.array([[-0.1, -0.2, -0.4], [1.5, 3.0, 6.0]]) >>> LUT = LUT3D( ... spow(LUT3D.linear_table(16, domain), 1 / 2.2), ... 'My LUT', ... np.array([[-0.1, -0.2, -0.4], [1.5, 3.0, 6.0]]), ... comments=['A first comment.', 'A second comment.']) >>> write_LUT_IridasCube(LUT, 'My_LUT.cube') # doctest: +SKIP """ if isinstance(LUT, LUTSequence): LUT = LUT[0] usage_warning('"LUT" is a "LUTSequence" instance was passed, ' 'using first sequence "LUT":\n' '{0}'.format(LUT)) assert not LUT.is_domain_explicit(), '"LUT" domain must be implicit!' if isinstance(LUT, LUT1D): LUT = LUT.as_LUT(LUT3x1D) assert (isinstance(LUT, LUT3x1D) or isinstance(LUT, LUT3D)), '"LUT" must be a 1D, 3x1D or 3D "LUT"!' is_3x1D = isinstance(LUT, LUT3x1D) size = LUT.size if is_3x1D: assert 2 <= size <= 65536, '"LUT" size must be in domain [2, 65536]!' else: assert 2 <= size <= 256, '"LUT" size must be in domain [2, 256]!' def _format_array(array): """ Formats given array as an *Iridas* *.cube* data row. """ return '{1:0.{0}f} {2:0.{0}f} {3:0.{0}f}'.format(decimals, *array) with open(path, 'w') as cube_file: cube_file.write('TITLE "{0}"\n'.format(LUT.name)) if LUT.comments: for comment in LUT.comments: cube_file.write('# {0}\n'.format(comment)) cube_file.write('{0} {1}\n'.format( 'LUT_1D_SIZE' if is_3x1D else 'LUT_3D_SIZE', LUT.table.shape[0])) default_domain = np.array([[0, 0, 0], [1, 1, 1]]) if not np.array_equal(LUT.domain, default_domain): cube_file.write('DOMAIN_MIN {0}\n'.format( _format_array(LUT.domain[0]))) cube_file.write('DOMAIN_MAX {0}\n'.format( _format_array(LUT.domain[1]))) if not is_3x1D: table = LUT.table.reshape([-1, 3], order='F') else: table = LUT.table for row in table: cube_file.write('{0}\n'.format(_format_array(row))) return True
def XYZ_to_Hunt(XYZ, XYZ_w, XYZ_b, L_A, surround=HUNT_VIEWING_CONDITIONS['Normal Scenes'], L_AS=None, CCT_w=None, XYZ_p=None, p=None, S=None, S_w=None, helson_judd_effect=False, discount_illuminant=True): """ Computes the *Hunt* colour appearance model correlates. Parameters ---------- XYZ : array_like *CIE XYZ* tristimulus values of test sample / stimulus. XYZ_w : array_like *CIE XYZ* tristimulus values of reference white. XYZ_b : array_like *CIE XYZ* tristimulus values of background. L_A : numeric or array_like Adapting field *luminance* :math:`L_A` in :math:`cd/m^2`. surround : Hunt_InductionFactors, optional Surround viewing conditions induction factors. L_AS : numeric or array_like, optional Scotopic luminance :math:`L_{AS}` of the illuminant, approximated if not specified. CCT_w : numeric or array_like, optional Correlated color temperature :math:`T_{cp}`: of the illuminant, needed to approximate :math:`L_{AS}`. XYZ_p : array_like, optional *CIE XYZ* tristimulus values of proximal field, assumed to be equal to background if not specified. p : numeric or array_like, optional Simultaneous contrast / assimilation factor :math:`p` with value normalised to domain [-1, 0] when simultaneous contrast occurs and normalised to domain [0, 1] when assimilation occurs. S : numeric or array_like, optional Scotopic response :math:`S` to the stimulus, approximated using tristimulus values :math:`Y` of the stimulus if not specified. S_w : numeric or array_like, optional Scotopic response :math:`S_w` for the reference white, approximated using the tristimulus values :math:`Y_w` of the reference white if not specified. helson_judd_effect : bool, optional Truth value indicating whether the *Helson-Judd* effect should be accounted for. discount_illuminant : bool, optional Truth value indicating if the illuminant should be discounted. Returns ------- Hunt_Specification *Hunt* colour appearance model specification. Raises ------ ValueError If an illegal arguments combination is specified. Notes ----- +--------------------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +==========================+=======================+===============+ | ``XYZ`` | [0, 100] | [0, 1] | +--------------------------+-----------------------+---------------+ | ``XYZ_w`` | [0, 100] | [0, 1] | +--------------------------+-----------------------+---------------+ | ``XYZ_b`` | [0, 100] | [0, 1] | +--------------------------+-----------------------+---------------+ | ``XYZ_p`` | [0, 100] | [0, 1] | +--------------------------+-----------------------+---------------+ +--------------------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +==========================+=======================+===============+ | ``Hunt_Specification.h`` | [0, 360] | [0, 1] | +--------------------------+-----------------------+---------------+ References ---------- :cite:`Fairchild2013u`, :cite:`Hunt2004b` Examples -------- >>> XYZ = np.array([19.01, 20.00, 21.78]) >>> XYZ_w = np.array([95.05, 100.00, 108.88]) >>> XYZ_b = np.array([95.05, 100.00, 108.88]) >>> L_A = 318.31 >>> surround = HUNT_VIEWING_CONDITIONS['Normal Scenes'] >>> CCT_w = 6504.0 >>> XYZ_to_Hunt(XYZ, XYZ_w, XYZ_b, L_A, surround, CCT_w=CCT_w) ... # doctest: +ELLIPSIS Hunt_Specification(J=30.0462678..., C=0.1210508..., h=269.2737594..., \ s=0.0199093..., Q=22.2097654..., M=0.1238964..., H=None, HC=None) """ XYZ = to_domain_100(XYZ) XYZ_w = to_domain_100(XYZ_w) XYZ_b = to_domain_100(XYZ_b) _X, Y, _Z = tsplit(XYZ) _X_w, Y_w, _Z_w = tsplit(XYZ_w) X_b, Y_b, _Z_b = tsplit(XYZ_b) # Arguments handling. if XYZ_p is not None: X_p, Y_p, Z_p = tsplit(to_domain_100(XYZ_p)) else: X_p = X_b Y_p = Y_b Z_p = Y_b usage_warning('Unspecified proximal field "XYZ_p" argument, using ' 'background "XYZ_b" as approximation!') if surround.N_cb is None: N_cb = 0.725 * spow(Y_w / Y_b, 0.2) usage_warning('Unspecified "N_cb" argument, using approximation: ' '"{0}"'.format(N_cb)) if surround.N_bb is None: N_bb = 0.725 * spow(Y_w / Y_b, 0.2) usage_warning('Unspecified "N_bb" argument, using approximation: ' '"{0}"'.format(N_bb)) if L_AS is None and CCT_w is None: raise ValueError('Either the scotopic luminance "L_AS" of the ' 'illuminant or its correlated colour temperature ' '"CCT_w" must be specified!') if L_AS is None: L_AS = illuminant_scotopic_luminance(L_A, CCT_w) usage_warning( 'Unspecified "L_AS" argument, using approximation from "CCT": ' '"{0}"'.format(L_AS)) if (S is None and S_w is not None) or (S is not None and S_w is None): raise ValueError('Either both stimulus scotopic response "S" and ' 'reference white scotopic response "S_w" arguments ' 'need to be specified or none of them!') elif S is None and S_w is None: S = Y S_w = Y_w usage_warning( 'Unspecified stimulus scotopic response "S" and reference ' 'white scotopic response "S_w" arguments, using ' 'approximation: "{0}", "{1}"'.format(S, S_w)) if p is None: usage_warning( 'Unspecified simultaneous contrast / assimilation "p" ' 'argument, model will not account for simultaneous chromatic ' 'contrast!') XYZ_p = tstack([X_p, Y_p, Z_p]) # Computing luminance level adaptation factor :math:`F_L`. F_L = luminance_level_adaptation_factor(L_A) # Computing test sample chromatic adaptation. rgb_a = chromatic_adaptation(XYZ, XYZ_w, XYZ_b, L_A, F_L, XYZ_p, p, helson_judd_effect, discount_illuminant) # Computing reference white chromatic adaptation. rgb_aw = chromatic_adaptation(XYZ_w, XYZ_w, XYZ_b, L_A, F_L, XYZ_p, p, helson_judd_effect, discount_illuminant) # Computing opponent colour dimensions. # Computing achromatic post adaptation signals. A_a = achromatic_post_adaptation_signal(rgb_a) A_aw = achromatic_post_adaptation_signal(rgb_aw) # Computing colour difference signals. C = colour_difference_signals(rgb_a) C_w = colour_difference_signals(rgb_aw) # ------------------------------------------------------------------------- # Computing the *hue* angle :math:`h_s`. # ------------------------------------------------------------------------- h = hue_angle(C) # hue_w = hue_angle(C_w) # TODO: Implement hue quadrature & composition computation. # ------------------------------------------------------------------------- # Computing the correlate of *saturation* :math:`s`. # ------------------------------------------------------------------------- # Computing eccentricity factors. e_s = eccentricity_factor(h) # Computing low luminance tritanopia factor :math:`F_t`. F_t = low_luminance_tritanopia_factor(L_A) M_yb = yellowness_blueness_response(C, e_s, surround.N_c, N_cb, F_t) M_rg = redness_greenness_response(C, e_s, surround.N_c, N_cb) M_yb_w = yellowness_blueness_response(C_w, e_s, surround.N_c, N_cb, F_t) M_rg_w = redness_greenness_response(C_w, e_s, surround.N_c, N_cb) # Computing overall chromatic response. M = overall_chromatic_response(M_yb, M_rg) M_w = overall_chromatic_response(M_yb_w, M_rg_w) s = saturation_correlate(M, rgb_a) # ------------------------------------------------------------------------- # Computing the correlate of *brightness* :math:`Q`. # ------------------------------------------------------------------------- # Computing achromatic signal :math:`A`. A = achromatic_signal(L_AS, S, S_w, N_bb, A_a) A_w = achromatic_signal(L_AS, S_w, S_w, N_bb, A_aw) Q = brightness_correlate(A, A_w, M, surround.N_b) brightness_w = brightness_correlate(A_w, A_w, M_w, surround.N_b) # TODO: Implement whiteness-blackness :math:`Q_{wb}` computation. # ------------------------------------------------------------------------- # Computing the correlate of *Lightness* :math:`J`. # ------------------------------------------------------------------------- J = lightness_correlate(Y_b, Y_w, Q, brightness_w) # ------------------------------------------------------------------------- # Computing the correlate of *chroma* :math:`C_{94}`. # ------------------------------------------------------------------------- C_94 = chroma_correlate(s, Y_b, Y_w, Q, brightness_w) # ------------------------------------------------------------------------- # Computing the correlate of *colourfulness* :math:`M_{94}`. # ------------------------------------------------------------------------- M_94 = colourfulness_correlate(F_L, C_94) return Hunt_Specification(J, C_94, from_range_degrees(h), s, Q, M_94, None, None)
def detect_colour_checkers_segmentation( image: ArrayLike, samples: Integer = 16, additional_data: Boolean = False, **kwargs: Any, ) -> Union[Tuple[ColourCheckerSwatchesData, ...], Tuple[NDArray, ...]]: """ Detect the colour checkers swatches in given image using segmentation. Parameters ---------- image : array_like Image to detect the colour checkers swatches in. samples : int Samples count to use to compute the swatches colours. The effective samples count is :math:`samples^2`. additional_data : bool, optional Whether to output additional data. Other Parameters ---------------- aspect_ratio Colour checker aspect ratio, e.g. 1.5. aspect_ratio_minimum Minimum colour checker aspect ratio for detection: projective geometry might reduce the colour checker aspect ratio. aspect_ratio_maximum Maximum colour checker aspect ratio for detection: projective geometry might increase the colour checker aspect ratio. swatches Colour checker swatches total count. swatches_horizontal Colour checker swatches horizontal columns count. swatches_vertical Colour checker swatches vertical row count. swatches_count_minimum Minimum swatches count to be considered for the detection. swatches_count_maximum Maximum swatches count to be considered for the detection. swatches_chromatic_slice A `slice` instance defining chromatic swatches used to detect if the colour checker is upside down. swatches_achromatic_slice A `slice` instance defining achromatic swatches used to detect if the colour checker is upside down. swatch_minimum_area_factor Swatch minimum area factor :math:`f` with the minimum area :math:`m_a` expressed as follows: :math:`m_a = image_w * image_h / s_c / f` where :math:`image_w`, :math:`image_h` and :math:`s_c` are respectively the image width, height and the swatches count. swatch_contour_scale As the image is filtered, the swatches area will tend to shrink, the generated contours can thus be scaled. cluster_contour_scale As the swatches are clustered, it might be necessary to adjust the cluster scale so that the masks are centred better on the swatches. working_width Size the input image is resized to for detection. fast_non_local_means_denoising_kwargs Keyword arguments for :func:`cv2.fastNlMeansDenoising` definition. adaptive_threshold_kwargs Keyword arguments for :func:`cv2.adaptiveThreshold` definition. interpolation_method Interpolation method used when resizing the images, `cv2.INTER_CUBIC` and `cv2.INTER_LINEAR` methods are recommended. Returns ------- :class`tuple` Tuple of :class:`ColourCheckerSwatchesData` class instances or colour checkers swatches. Examples -------- >>> import os >>> from colour import read_image >>> from colour_checker_detection import TESTS_RESOURCES_DIRECTORY >>> path = os.path.join(TESTS_RESOURCES_DIRECTORY, ... 'colour_checker_detection', 'detection', ... 'IMG_1967.png') >>> image = read_image(path) >>> detect_colour_checkers_segmentation(image) # doctest: +SKIP (array([[ 0.361626... , 0.2241066..., 0.1187837...], [ 0.6280594..., 0.3950883..., 0.2434766...], [ 0.3326232..., 0.3156182..., 0.2891038...], [ 0.3048414..., 0.2738973..., 0.1069985...], [ 0.4174869..., 0.3199669..., 0.3081552...], [ 0.347873 ..., 0.4413193..., 0.2931614...], [ 0.6816301..., 0.3539050..., 0.0753397...], [ 0.2731050..., 0.2528467..., 0.3312920...], [ 0.6192335..., 0.2703833..., 0.1866387...], [ 0.3068567..., 0.1803366..., 0.1919807...], [ 0.4866354..., 0.4594004..., 0.0374186...], [ 0.6518523..., 0.4010608..., 0.0171886...], [ 0.1941571..., 0.1855801..., 0.2750632...], [ 0.2799946..., 0.3854609..., 0.1241038...], [ 0.5537481..., 0.2139004..., 0.1267332...], [ 0.7208045..., 0.5152904..., 0.0061946...], [ 0.5778360..., 0.2578533..., 0.2687992...], [ 0.1809450..., 0.3174742..., 0.2959902...], [ 0.7427522..., 0.6107554..., 0.4398439...], [ 0.6296108..., 0.5177606..., 0.3728032...], [ 0.5139589..., 0.4216307..., 0.2992694...], [ 0.3704401..., 0.3033927..., 0.2093089...], [ 0.2641854..., 0.2154007..., 0.1441267...], [ 0.1650098..., 0.1345239..., 0.0817437...]], dtype=float32),) """ image = as_float_array(image, FLOAT_DTYPE_DEFAULT)[..., :3] settings = Structure(**SETTINGS_SEGMENTATION_COLORCHECKER_CLASSIC) settings.update(**kwargs) image = adjust_image(image, settings.working_width, settings.interpolation_method) swatches_h, swatches_v = ( settings.swatches_horizontal, settings.swatches_vertical, ) colour_checkers_colours = [] colour_checkers_data = [] for colour_checker in extract_colour_checkers_segmentation( image, **settings): width, height = colour_checker.shape[1], colour_checker.shape[0] masks = swatch_masks(width, height, swatches_h, swatches_v, samples) swatch_colours = [] for mask in masks: swatch_colours.append( np.mean( colour_checker[mask[0]:mask[1], mask[2]:mask[3], ...], axis=(0, 1), )) # The colour checker might be flipped: The mean standard deviation # of some expected normalised chromatic and achromatic neutral # swatches is computed. If the chromatic mean is lesser than the # achromatic mean, it means that the colour checker is flipped. std_means = [] for slice_ in [ settings.swatches_chromatic_slice, settings.swatches_achromatic_slice, ]: swatch_std_mean = as_float_array(swatch_colours[slice_]) swatch_std_mean /= swatch_std_mean[..., 1][..., np.newaxis] std_means.append(np.mean(np.std(swatch_std_mean, 0))) if std_means[0] < std_means[1]: usage_warning("Colour checker was seemingly flipped," " reversing the samples!") swatch_colours = swatch_colours[::-1] colour_checkers_colours.append(np.asarray(swatch_colours)) colour_checkers_data.append((colour_checker, masks)) if additional_data: return tuple( ColourCheckerSwatchesData(tuple(colour_checkers_colours[i]), * colour_checkers_data[i]) for i, colour_checker_colours in enumerate(colour_checkers_colours)) else: return tuple(colour_checkers_colours)
warning('This is a fifth warning and it has been filtered!') filter_warnings(False, python_warnings=False) warning('This is a sixth warning and it has not been filtered!') filter_warnings(False, python_warnings=False) filter_warnings(colour_warnings=False, colour_runtime_warnings=True) runtime_warning('This is a first runtime warning and it has been filtered!') filter_warnings(colour_warnings=False, colour_usage_warnings=True) usage_warning('This is a first usage warning and it has been filtered!') print('\n') message_box('Overall "Colour" Examples') message_box('N-Dimensional Arrays Support') XYZ = np.array([0.20654008, 0.12197225, 0.05136952]) illuminant = np.array([0.31270, 0.32900]) message_box('Using 1d "array_like" parameter:\n' '\n{0}'.format(XYZ)) print(colour.XYZ_to_Lab(XYZ, illuminant=illuminant)) print('\n') XYZ = np.tile(XYZ, (6, 1))
def colour_fidelity_index_CIE2017( sd_test: SpectralDistribution, additional_data: Boolean = False ) -> Union[Floating, ColourRendering_Specification_CIE2017]: """ Return the *CIE 2017 Colour Fidelity Index* (CFI) :math:`R_f` of given spectral distribution. Parameters ---------- sd_test Test spectral distribution. additional_data Whether to output additional data. Returns ------- :class:`numpy.floating` or \ :class:`colour.quality.ColourRendering_Specification_CIE2017` *CIE 2017 Colour Fidelity Index* (CFI) :math:`R_f`. References ---------- :cite:`CIETC1-902017` Examples -------- >>> from colour.colorimetry import SDS_ILLUMINANTS >>> sd = SDS_ILLUMINANTS['FL2'] >>> colour_fidelity_index_CIE2017(sd) # doctest: +ELLIPSIS 70.1208254... """ if sd_test.shape.start > 380 or sd_test.shape.end < 780: usage_warning("Test spectral distribution shape does not span the" "recommended 380-780nm range, missing values will be" "filled with zeros!") # NOTE: "CIE 2017 Colour Fidelity Index" standard recommends filling # missing values with zeros. sd_test = cast(SpectralDistribution, sd_test.copy()) sd_test.extrapolator = Extrapolator sd_test.extrapolator_kwargs = { "method": "constant", "left": 0, "right": 0, } if sd_test.shape.interval > 5: raise ValueError("Test spectral distribution interval is greater than" "5nm which is the maximum recommended value " 'for computing the "CIE 2017 Colour Fidelity Index"!') shape = SpectralShape( SPECTRAL_SHAPE_CIE2017.start, SPECTRAL_SHAPE_CIE2017.end, sd_test.shape.interval, ) CCT, D_uv = tsplit(CCT_reference_illuminant(sd_test)) sd_reference = sd_reference_illuminant(CCT, shape) # NOTE: All computations except CCT calculation use the # "CIE 1964 10 Degree Standard Observer". # pylint: disable=E1102 cmfs_10 = reshape_msds(MSDS_CMFS["CIE 1964 10 Degree Standard Observer"], shape) # pylint: disable=E1102 sds_tcs = reshape_msds(load_TCS_CIE2017(shape), shape) test_tcs_colorimetry_data = tcs_colorimetry_data(sd_test, sds_tcs, cmfs_10) reference_tcs_colorimetry_data = tcs_colorimetry_data( sd_reference, sds_tcs, cmfs_10) delta_E_s = np.empty(len(sds_tcs.labels)) for i, _delta_E in enumerate(delta_E_s): delta_E_s[i] = euclidean_distance( test_tcs_colorimetry_data[i].Jpapbp, reference_tcs_colorimetry_data[i].Jpapbp, ) R_s = as_float_array(delta_E_to_R_f(delta_E_s)) R_f = as_float_scalar(delta_E_to_R_f(np.average(delta_E_s))) if additional_data: return ColourRendering_Specification_CIE2017( sd_test.name, sd_reference, R_f, R_s, CCT, D_uv, (test_tcs_colorimetry_data, reference_tcs_colorimetry_data), delta_E_s, ) else: return R_f
def write_image(image, path, bit_depth='float32', method='OpenImageIO', **kwargs): """ Writes given image at given path using given method. Parameters ---------- image : array_like Image data. path : unicode Image path. bit_depth : unicode, optional **{'float32', 'uint8', 'uint16', 'float16'}**, Bit depth to write the image at, for the *Imageio* method, the image data is converted with :func:`colour.io.convert_bit_depth` definition prior to writing the image. method : unicode, optional **{'OpenImageIO', 'Imageio'}**, Write method, i.e. the image library used for writing images. Other Parameters ---------------- attributes : array_like, optional {:func:`colour.io.write_image_OpenImageIO`}, An array of :class:`colour.io.ImageAttribute_Specification` class instances used to set attributes of the image. Returns ------- bool Definition success. Notes ----- - If the given method is *OpenImageIO* but the library is not available writing will be performed by *Imageio*. - If the given method is *Imageio*, ``kwargs`` is passed directly to the wrapped definition. Examples -------- Basic image writing: >>> import os >>> import colour >>> path = os.path.join(colour.__path__[0], 'io', 'tests', 'resources', ... 'CMS_Test_Pattern.exr') >>> image = read_image(path) # doctest: +SKIP >>> path = os.path.join(colour.__path__[0], 'io', 'tests', 'resources', ... 'CMSTestPattern.tif') >>> write_image(image, path) # doctest: +SKIP True Advanced image writing while setting attributes using *OpenImageIO*: >>> compression = ImageAttribute_Specification('Compression', 'none') >>> write_image(image, path, bit_depth='uint8', attributes=[compression]) ... # doctest: +SKIP True """ if method.lower() == 'openimageio': # pragma: no cover if not is_openimageio_installed(): usage_warning( '"OpenImageIO" related API features are not available, ' 'switching to "Imageio"!') method = 'Imageio' function = WRITE_IMAGE_METHODS[method] if method.lower() == 'openimageio': # pragma: no cover kwargs = filter_kwargs(function, **kwargs) return function(image, path, bit_depth, **kwargs)
def colour_fidelity_index_CIE2017(sd_test, additional_data=False): """ Returns the *CIE 2017 Colour Fidelity Index* (CFI) :math:`R_f` of given spectral distribution. Parameters ---------- sd_test : SpectralDistribution Test spectral distribution. additional_data : bool, optional Whether to output additional data. Returns ------- numeric or ColourRendering_Specification_CIE2017 *CIE 2017 Colour Fidelity Index* (CFI) :math:`R_f`. References ---------- :cite:`CIETC1-902017` Examples -------- >>> from colour.colorimetry import SDS_ILLUMINANTS >>> sd = SDS_ILLUMINANTS['FL2'] >>> colour_fidelity_index_CIE2017(sd) # doctest: +ELLIPSIS 70.1208254... """ if sd_test.shape.start > 380 or sd_test.shape.end < 780: usage_warning('Test spectral distribution shape does not span the' 'recommended 380-780nm range, missing values will be' 'filled with zeros!') # NOTE: "CIE 2017 Colour Fidelity Index" standard recommends filling # missing values with zeros. sd_test = sd_test.copy() sd_test.extrapolator = Extrapolator sd_test.extrapolator_kwargs = { 'method': 'constant', 'left': 0, 'right': 0 } if sd_test.shape.interval > 5: raise ValueError('Test spectral distribution interval is greater than' '5nm which is the maximum recommended value ' 'for computing the "CIE 2017 Colour Fidelity Index"!') shape = SpectralShape(SPECTRAL_SHAPE_CIE2017.start, SPECTRAL_SHAPE_CIE2017.end, sd_test.shape.interval) CCT, D_uv = CCT_reference_illuminant(sd_test) sd_reference = sd_reference_illuminant(CCT, shape) # NOTE: All computations except CCT calculation use the # "CIE 1964 10 Degree Standard Observer". cmfs_10 = MSDS_CMFS['CIE 1964 10 Degree Standard Observer'].copy().align( shape) sds_tcs = load_TCS_CIE2017(shape).align(shape) test_tcs_colorimetry_data = tcs_colorimetry_data(sd_test, sds_tcs, cmfs_10) reference_tcs_colorimetry_data = tcs_colorimetry_data( sd_reference, sds_tcs, cmfs_10) delta_E_s = np.empty(len(sds_tcs.labels)) for i, _delta_E in enumerate(delta_E_s): delta_E_s[i] = euclidean_distance( test_tcs_colorimetry_data[i].Jpapbp, reference_tcs_colorimetry_data[i].Jpapbp) R_s = delta_E_to_R_f(delta_E_s) R_f = delta_E_to_R_f(np.average(delta_E_s)) if additional_data: return ColourRendering_Specification_CIE2017( sd_test.name, sd_reference, R_f, R_s, CCT, D_uv, (test_tcs_colorimetry_data, reference_tcs_colorimetry_data), delta_E_s) else: return R_f
def ootf_reverse_BT2100_HLG(F_D, L_B=0, L_W=1000, gamma=None): """ Defines *Recommendation ITU-R BT.2100* *Reference HLG* reverse opto-optical transfer function (OOTF / OOCF). Parameters ---------- F_D : numeric or array_like :math:`F_D` is the luminance of a displayed linear component :math:`{R_D, G_D, or B_D}`, in :math:`cd/m^2`. L_B : numeric, optional :math:`L_B` is the display luminance for black in :math:`cd/m^2`. L_W : numeric, optional :math:`L_W` is nominal peak luminance of the display in :math:`cd/m^2` for achromatic pixels. gamma : numeric, optional System gamma value, 1.2 at the nominal display peak luminance of :math:`1000 cd/m^2`. Returns ------- numeric or ndarray :math:`E` is the signal for each colour component :math:`{R_S, G_S, B_S}` proportional to scene linear light and scaled by camera exposure. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``F_D`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``E`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`Borer2017a`, :cite:`InternationalTelecommunicationUnion2016a` Examples -------- >>> ootf_reverse_BT2100_HLG(63.095734448019336) # doctest: +ELLIPSIS 0.1000000... """ F_D = np.atleast_1d(to_domain_1(F_D)) if F_D.shape[-1] != 3: usage_warning( '"Recommendation ITU-R BT.2100" "Reference HLG OOTF" uses ' 'RGB Luminance in computations and expects a vector input, thus ' 'the given input array will be stacked to compose a vector for ' 'internal computations but a single component will be output.') R_D = G_D = B_D = F_D else: R_D, G_D, B_D = tsplit(F_D) Y_D = np.sum(BT2100_HLG_WEIGHTS * tstack([R_D, G_D, B_D]), axis=-1) alpha = L_W - L_B beta = L_B if gamma is None: gamma = gamma_function_BT2100_HLG(L_W) R_S = np.where( Y_D == beta, 0.0, (np.abs((Y_D - beta) / alpha)**((1 - gamma) / gamma)) * (R_D - beta) / alpha, ) G_S = np.where( Y_D == beta, 0.0, (np.abs((Y_D - beta) / alpha)**((1 - gamma) / gamma)) * (G_D - beta) / alpha, ) B_S = np.where( Y_D == beta, 0.0, (np.abs((Y_D - beta) / alpha)**((1 - gamma) / gamma)) * (B_D - beta) / alpha, ) if F_D.shape[-1] != 3: return as_float(from_range_1(R_S)) else: RGB_S = tstack([R_S, G_S, B_S]) return from_range_1(RGB_S)
def write_LUT_SonySPI3D(LUT, path, decimals=7): """ Writes given *LUT* to given *Sony* *.spi3d* *LUT* file. Parameters ---------- LUT : LUT3D :class:`LUT3D` or :class:`LUTSequence` class instance to write at given path. path : unicode *LUT* path. decimals : int, optional Formatting decimals. Returns ------- bool Definition success. Warning ------- - If a :class:`LUTSequence` class instance is passed as ``LUT``, the first *LUT* in the *LUT* sequence will be used. Examples -------- Writing a 3D *Sony* *.spi3d* *LUT*: >>> LUT = LUT3D( ... LUT3D.linear_table(16) ** (1 / 2.2), ... 'My LUT', ... np.array([[0, 0, 0], [1, 1, 1]]), ... comments=['A first comment.', 'A second comment.']) >>> write_LUT_SonySPI3D(LUT, 'My_LUT.cube') # doctest: +SKIP """ if isinstance(LUT, LUTSequence): LUT = LUT[0] usage_warning('"LUT" is a "LUTSequence" instance was passed, ' 'using first sequence "LUT":\n' '{0}'.format(LUT)) assert not LUT.is_domain_explicit(), '"LUT" domain must be implicit!' assert isinstance(LUT, LUT3D), '"LUT" must be either a 3D "LUT"!' assert np.array_equal(LUT.domain, np.array([ [0, 0, 0], [1, 1, 1], ])), '"LUT" domain must be [[0, 0, 0], [1, 1, 1]]!' def _format_array(array): """ Formats given array as a *Sony* *.spi3d* data row. """ return '{1:d} {2:d} {3:d} {4:0.{0}f} {5:0.{0}f} {6:0.{0}f}'.format( decimals, *array) with open(path, 'w') as spi3d_file: spi3d_file.write('SPILUT 1.0\n') spi3d_file.write('3 3\n') spi3d_file.write('{0} {0} {0}\n'.format(LUT.size)) indexes = DEFAULT_INT_DTYPE( LUT.linear_table(LUT.size) * (LUT.size - 1)).reshape([-1, 3]) table = LUT.table.reshape([-1, 3]) for i, row in enumerate(indexes): spi3d_file.write('{0}\n'.format( _format_array(list(row) + list(table[i])))) if LUT.comments: for comment in LUT.comments: spi3d_file.write('# {0}\n'.format(comment)) return True
def convert(a, source, target, **kwargs): """ Converts given object :math:`a` from source colour representation to target colour representation using the automatic colour conversion graph. The conversion is performed by finding the shortest path in a `NetworkX <https://networkx.github.io/>`_ :class:`DiGraph` class instance. The conversion path adopts the **'1'** domain-range scale and the object :math:`a` is expected to be *soft* normalised accordingly. For example, *CIE XYZ* tristimulus values arguments for use with the *CAM16* colour appearance model should be in domain `[0, 1]` instead of the domain `[0, 100]` used with the **'Reference'** domain-range scale. The arguments are typically converted as follows: - *Scalars* in domain-range `[0, 10]`, e.g *Munsell Value* are scaled by *10*. - *Percentages* in domain-range `[0, 100]` are scaled by *100*. - *Degrees* in domain-range `[0, 360]` are scaled by *360*. - *Integers* in domain-range `[0, 2**n -1]` where `n` is the bit depth are scaled by *2**n -1*. See the `Domain-Range Scales <../basics.html#domain-range-scales>`_ page for more information. Parameters ---------- a : array_like or numeric or SpectralDistribution Object :math:`a` to convert. If :math:`a` represents a reflectance, transmittance or absorptance value, the expectation is that it is viewed under *CIE Standard Illuminant D Series* *D65*. The illuminant can be changed on a per definition basis along the conversion path. source : unicode Source colour representation, i.e. the source node in the automatic colour conversion graph. target : unicode Target colour representation, i.e. the target node in the automatic colour conversion graph. Other Parameters ---------------- \\**kwargs : dict, optional {'\\*'}, Please refer to the documentation of the supported conversion definitions. Arguments for the conversion definitions are passed as keyword arguments whose names is those of the conversion definitions and values set as dictionaries. For example, in the conversion from spectral distribution to *sRGB* colourspace, passing arguments to the :func:`colour.sd_to_XYZ` definition is done as follows:: convert(sd, 'Spectral Distribution', 'sRGB', sd_to_XYZ={\ 'illuminant': ILLUMINANTS_SDS['FL2']}) It is also possible to pass keyword arguments directly to the various conversion definitions irrespective of their name. This is ``dangerous`` and could cause unexpected behaviour because of unavoidable discrepancies with the underlying :func:`colour.utilities.filter_kwargs` definition between Python 2.7 and 3.x. Using this direct keyword arguments passing mechanism might also ends up passing incompatible arguments to a given conversion definition. Consider the following conversion:: convert(sd, 'Spectral Distribution', 'sRGB', 'illuminant': \ ILLUMINANTS_SDS['FL2']) Because both the :func:`colour.sd_to_XYZ` and :func:`colour.XYZ_to_sRGB` definitions have an *illuminant* argument, `ILLUMINANTS_SDS['FL2']` will be passed to both of them and will raise an exception in the :func:`colour.XYZ_to_sRGB` definition. This will be addressed in the future by either catching the exception and trying a new time without the keyword argument or more elegantly via type checking. With that in mind, this mechanism offers some good benefits: For example, it allows defining a conversion from *CIE XYZ* colourspace to *n* different colour models while passing an illuminant argument but without having to explicitly define all the explicit conversion definition arguments:: a = np.array([0.20654008, 0.12197225, 0.05136952]) illuminant = ILLUMINANTS['CIE 1931 2 Degree Standard Observer']\ ['D65'] for model in ('CIE xyY', 'CIE Lab'): convert(a, 'CIE XYZ', model, illuminant=illuminant) Instead of:: for model in ('CIE xyY', 'CIE Lab'): convert(a, 'CIE XYZ', model, XYZ_to_xyY={'illuminant': \ illuminant}, XYZ_to_Lab={'illuminant': illuminant}) Mixing both approaches is possible for the brevity benefits. It is made possible because the keyword arguments directly passed are filtered first and then the resulting dict is updated with the explicit conversion definition arguments:: illuminant = ILLUMINANTS['CIE 1931 2 Degree Standard Observer']\ ['D65'] convert(sd, 'Spectral Distribution', 'sRGB', 'illuminant': \ ILLUMINANTS_SDS['FL2'], XYZ_to_sRGB={'illuminant': illuminant}) For inspection purposes, verbose is enabled by passing arguments to the :func:`colour.describe_conversion_path` definition via the ``verbose`` keyword argument as follows:: convert(sd, 'Spectral Distribution', 'sRGB', \ verbose={'mode': 'Long'}) Returns ------- ndarray or numeric or SpectralDistribution Converted object :math:`a`. Warnings -------- The domain-range scale is **'1'** and cannot be changed. Notes ----- - Various defaults have been systematically adopted compared to the low-level *Colour* API: - The default illuminant for the computation is *CIE Standard Illuminant D Series* *D65*. It can be changed on a per definition basis along the conversion path. - The default *RGB* colourspace is the *sRGB* colourspace. It can also be changed on a per definition basis along the conversion path. - Most of the colour appearance models have defaults set according to *IEC 61966-2-1:1999* viewing conditions, i.e. *sRGB* 64 Lux ambiant illumination, 80 :math:`cd/m^2`, adapting field luminance about 20% of a white object in the scene. - The **RGB** colour representation is assumed to be linear and representing *scene-referred* imagery. To convert such *RGB* values to *output-referred* (*display-referred*) imagery, i.e. encode the *RGB* values using an encoding colour component transfer function (Encoding CCTF) / opto-electronic transfer function (OETF / OECF), the **Output-Referred RGB** representation must be used. Examples -------- >>> from colour import COLOURCHECKERS_SDS >>> sd = COLOURCHECKERS_SDS['ColorChecker N Ohta']['dark skin'] >>> convert(sd, 'Spectral Distribution', 'sRGB', ... verbose={'mode': 'Short', 'width': 75}) ... # doctest: +ELLIPSIS =========================================================================== * * * [ Conversion Path ] * * * * "sd_to_XYZ" --> "XYZ_to_sRGB" * * * =========================================================================== array([ 0.4567579..., 0.3098698..., 0.2486192...]) >>> illuminant = ILLUMINANTS_SDS['FL2'] >>> convert(sd, 'Spectral Distribution', 'sRGB', ... sd_to_XYZ={'illuminant': illuminant}) ... # doctest: +ELLIPSIS array([ 0.4792457..., 0.3167696..., 0.1736272...]) >>> a = np.array([0.45675795, 0.30986982, 0.24861924]) >>> convert(a, 'Output-Referred RGB', 'CAM16UCS') ... # doctest: +ELLIPSIS array([ 0.3999481..., 0.0920655..., 0.0812752...]) >>> a = np.array([0.39994811, 0.09206558, 0.08127526]) >>> convert(a, 'CAM16UCS', 'sRGB', verbose={'mode': 'Short', 'width': 75}) ... # doctest: +ELLIPSIS =========================================================================== * * * [ Conversion Path ] * * * * "UCS_Luo2006_to_JMh_CIECAM02" --> "JMh_CAM16_to_CAM16" --> * * "CAM16_to_XYZ" --> "XYZ_to_sRGB" * * * =========================================================================== array([ 0.4567576..., 0.3098826..., 0.2486222...]) """ # TODO: Remove the following warning whenever the automatic colour # conversion graph implementation is considered stable. usage_warning( 'The "Automatic Colour Conversion Graph" is a beta feature, be ' 'mindful of this when using it. Please report any unexpected ' 'behaviour and do not hesitate to ask any questions should they arise.' '\nThis warning can be disabled with the ' '"colour.utilities.suppress_warnings" context manager as follows:\n' 'with colour.utilities.suppress_warnings(colour_usage_warnings=True): ' '\n convert(*args, **kwargs)') source, target = source.lower(), target.lower() conversion_path = _conversion_path(source, target) verbose_kwargs = copy(kwargs) for conversion_function in conversion_path: conversion_function_name = _lower_order_function( conversion_function).__name__ # Filtering compatible keyword arguments passed directly and # irrespective of any conversion function name. filtered_kwargs = filter_kwargs(conversion_function, **kwargs) # Filtering keyword arguments passed as dictionary with the # conversion function name. filtered_kwargs.update(kwargs.get(conversion_function_name, {})) a = conversion_function(a, **filtered_kwargs) if conversion_function_name in verbose_kwargs: verbose_kwargs[conversion_function_name]['return'] = a else: verbose_kwargs[conversion_function_name] = {'return': a} if 'verbose' in verbose_kwargs: verbose_kwargs.update(verbose_kwargs.pop('verbose')) describe_conversion_path(source, target, **verbose_kwargs) return a
def anomalous_trichromacy_cmfs_Machado2009(cmfs, d_LMS): """ Shifts given *LMS* cone fundamentals colour matching functions with given :math:`\\Delta_{LMS}` shift amount in nanometers to simulate anomalous trichromacy using *Machado et al. (2009)* method. Parameters ---------- cmfs : LMS_ConeFundamentals *LMS* cone fundamentals colour matching functions. d_LMS : array_like :math:`\\Delta_{LMS}` shift amount in nanometers. Notes ----- - Input *LMS* cone fundamentals colour matching functions interval is expected to be 1 nanometer, incompatible input will be interpolated at 1 nanometer interval. - Input :math:`\\Delta_{LMS}` shift amount is in domain [0, 20]. Returns ------- LMS_ConeFundamentals Anomalous trichromacy *LMS* cone fundamentals colour matching functions. Warning ------- *Machado et al. (2009)* simulation of tritanomaly is based on the shift paradigm as an approximation to the actual phenomenon and restrain the model from trying to model tritanopia. The pre-generated matrices are using a shift value in domain [5, 59] contrary to the domain [0, 20] used for protanomaly and deuteranomaly simulation. References ---------- :cite:`Colblindorb`, :cite:`Colblindora`, :cite:`Colblindorc`, :cite:`Machado2009` Examples -------- >>> from colour import LMS_CMFS >>> cmfs = LMS_CMFS['Stockman & Sharpe 2 Degree Cone Fundamentals'] >>> cmfs[450] array([ 0.0498639, 0.0870524, 0.955393 ]) >>> anomalous_trichromacy_cmfs_Machado2009(cmfs, np.array([15, 0, 0]))[450] ... # doctest: +ELLIPSIS array([ 0.0891288..., 0.0870524 , 0.955393 ]) """ cmfs = cmfs.copy() if cmfs.shape.interval != 1: cmfs.interpolate(SpectralShape(interval=1)) cmfs.extrapolator_args = {'method': 'Constant', 'left': 0, 'right': 0} L, M, _S = tsplit(cmfs.values) d_L, d_M, d_S = tsplit(d_LMS) if d_S != 0: usage_warning( '"Machado et al. (2009)" simulation of tritanomaly is based on ' 'the shift paradigm as an approximation to the actual phenomenon ' 'and restrain the model from trying to model tritanopia.\n' 'The pre-generated matrices are using a shift value in domain ' '[5, 59] contrary to the domain [0, 20] used for protanomaly and ' 'deuteranomaly simulation.') area_L = np.trapz(L, cmfs.wavelengths) area_M = np.trapz(M, cmfs.wavelengths) def alpha(x): """ Computes :math:`alpha` factor. """ return (20 - x) / 20 # Corrected equations as per: # http://www.inf.ufrgs.br/~oliveira/pubs_files/ # CVD_Simulation/CVD_Simulation.html#Errata L_a = alpha(d_L) * L + 0.96 * area_L / area_M * (1 - alpha(d_L)) * M M_a = alpha(d_M) * M + 1 / 0.96 * area_M / area_L * (1 - alpha(d_M)) * L S_a = cmfs[cmfs.wavelengths - d_S][:, 2] LMS_a = tstack([L_a, M_a, S_a]) cmfs[cmfs.wavelengths] = LMS_a severity = '{0}, {1}, {2}'.format(d_L, d_M, d_S) template = '{0} - Anomalous Trichromacy ({1})' cmfs.name = template.format(cmfs.name, severity) cmfs.strict_name = template.format(cmfs.strict_name, severity) return cmfs
def write_LUT_SonySPI1D(LUT, path, decimals=7): """ Writes given *LUT* to given *Sony* *.spi1d* *LUT* file. Parameters ---------- LUT : LUT1D or LUT2d :class:`LUT1D`, :class:`LUT2D` or :class:`LUTSequence` class instance to write at given path. path : unicode *LUT* path. decimals : int, optional Formatting decimals. Returns ------- bool Definition success. Warning ------- - If a :class:`LUTSequence` class instance is passed as ``LUT``, the first *LUT* in the *LUT* sequence will be used. Examples -------- Writing a 1D *Sony* *.spi1d* *LUT*: >>> from colour.algebra import spow >>> domain = np.array([-0.1, 1.5]) >>> LUT = LUT1D( ... spow(LUT1D.linear_table(16), 1 / 2.2), ... 'My LUT', ... domain, ... comments=['A first comment.', 'A second comment.']) >>> write_LUT_SonySPI1D(LUT, 'My_LUT.cube') # doctest: +SKIP Writing a 2D *Sony* *.spi1d* *LUT*: >>> domain = np.array([[-0.1, -0.1, -0.1], [1.5, 1.5, 1.5]]) >>> LUT = LUT2D( ... spow(LUT2D.linear_table(16), 1 / 2.2), ... 'My LUT', ... domain, ... comments=['A first comment.', 'A second comment.']) >>> write_LUT_SonySPI1D(LUT, 'My_LUT.cube') # doctest: +SKIP """ if isinstance(LUT, LUTSequence): LUT = LUT[0] usage_warning('"LUT" is a "LUTSequence" instance was passed, ' 'using first sequence "LUT":\n' '{0}'.format(LUT)) assert not LUT.is_domain_explicit(), '"LUT" domain must be implicit!' assert (isinstance(LUT, LUT1D) or isinstance( LUT, LUT2D)), '"LUT" must be either a 1D or 2D "LUT"!' is_1D = isinstance(LUT, LUT1D) if is_1D: domain = LUT.domain else: domain = np.unique(LUT.domain) assert len(domain) == 2, 'Non-uniform "LUT" domain is unsupported!' def _format_array(array): """ Formats given array as a *Sony* *.spi1d* data row. """ return ' {1:0.{0}f} {2:0.{0}f} {3:0.{0}f}'.format(decimals, *array) with open(path, 'w') as spi1d_file: spi1d_file.write('Version 1\n') spi1d_file.write('From {1:0.{0}f} {2:0.{0}f}\n'.format( decimals, *domain)) spi1d_file.write('Length {0}\n'.format( LUT.table.size if is_1D else LUT.table.shape[0])) spi1d_file.write('Components {0}\n'.format(1 if is_1D else 3)) spi1d_file.write('{\n') for row in LUT.table: if is_1D: spi1d_file.write(' {1:0.{0}f}\n'.format(decimals, row)) else: spi1d_file.write('{0}\n'.format(_format_array(row))) spi1d_file.write('}\n') if LUT.comments: for comment in LUT.comments: spi1d_file.write('# {0}\n'.format(comment)) return True
def msds_cmfs_anomalous_trichromacy_Machado2009( cmfs: LMS_ConeFundamentals, d_LMS: ArrayLike) -> LMS_ConeFundamentals: """ Shift given *LMS* cone fundamentals colour matching functions with given :math:`\\Delta_{LMS}` shift amount in nanometers to simulate anomalous trichromacy using *Machado et al. (2009)* method. Parameters ---------- cmfs *LMS* cone fundamentals colour matching functions. d_LMS :math:`\\Delta_{LMS}` shift amount in nanometers. Notes ----- - Input *LMS* cone fundamentals colour matching functions interval is expected to be 1 nanometer, incompatible input will be interpolated at 1 nanometer interval. - Input :math:`\\Delta_{LMS}` shift amount is in domain [0, 20]. Returns ------- :class:`colour.LMS_ConeFundamentals` Anomalous trichromacy *LMS* cone fundamentals colour matching functions. Warnings -------- *Machado et al. (2009)* simulation of tritanomaly is based on the shift paradigm as an approximation to the actual phenomenon and restrain the model from trying to model tritanopia. The pre-generated matrices are using a shift value in domain [5, 59] contrary to the domain [0, 20] used for protanomaly and deuteranomaly simulation. References ---------- :cite:`Colblindorb`, :cite:`Colblindora`, :cite:`Colblindorc`, :cite:`Machado2009` Examples -------- >>> from colour.colorimetry import MSDS_CMFS_LMS >>> cmfs = MSDS_CMFS_LMS['Stockman & Sharpe 2 Degree Cone Fundamentals'] >>> cmfs[450] array([ 0.0498639, 0.0870524, 0.955393 ]) >>> msds_cmfs_anomalous_trichromacy_Machado2009( ... cmfs, np.array([15, 0, 0]))[450] # doctest: +ELLIPSIS array([ 0.0891288..., 0.0870524 , 0.955393 ]) """ cmfs = cast(LMS_ConeFundamentals, cmfs.copy()) if cmfs.shape.interval != 1: cmfs.interpolate(SpectralShape(cmfs.shape.start, cmfs.shape.end, 1)) cmfs.extrapolator_kwargs = {"method": "Constant", "left": 0, "right": 0} L, M, _S = tsplit(cmfs.values) d_L, d_M, d_S = tsplit(d_LMS) if d_S != 0: usage_warning( '"Machado et al. (2009)" simulation of tritanomaly is based on ' "the shift paradigm as an approximation to the actual phenomenon " "and restrain the model from trying to model tritanopia.\n" "The pre-generated matrices are using a shift value in domain " "[5, 59] contrary to the domain [0, 20] used for protanomaly and " "deuteranomaly simulation.") area_L = np.trapz(L, cmfs.wavelengths) area_M = np.trapz(M, cmfs.wavelengths) def alpha(x: NDArray) -> NDArray: """Compute :math:`alpha` factor.""" return (20 - x) / 20 # Corrected equations as per: # http://www.inf.ufrgs.br/~oliveira/pubs_files/ # CVD_Simulation/CVD_Simulation.html#Errata L_a = alpha(d_L) * L + 0.96 * area_L / area_M * (1 - alpha(d_L)) * M M_a = alpha(d_M) * M + 1 / 0.96 * area_M / area_L * (1 - alpha(d_M)) * L S_a = as_float_array(cmfs[cmfs.wavelengths - d_S])[:, 2] LMS_a = tstack([L_a, M_a, S_a]) cmfs[cmfs.wavelengths] = LMS_a severity = f"{d_L}, {d_M}, {d_S}" template = "{0} - Anomalous Trichromacy ({1})" cmfs.name = template.format(cmfs.name, severity) cmfs.strict_name = template.format(cmfs.strict_name, severity) return cmfs
def chromatic_adaptation_CIE1994(XYZ_1, xy_o1, xy_o2, Y_o, E_o1, E_o2, n=1): """ Adapts given stimulus *CIE XYZ_1* tristimulus values from test viewing conditions to reference viewing conditions using *CIE 1994* chromatic adaptation model. Parameters ---------- XYZ_1 : array_like *CIE XYZ* tristimulus values of test sample / stimulus. xy_o1 : array_like Chromaticity coordinates :math:`x_{o1}` and :math:`y_{o1}` of test illuminant and background. xy_o2 : array_like Chromaticity coordinates :math:`x_{o2}` and :math:`y_{o2}` of reference illuminant and background. Y_o : numeric Luminance factor :math:`Y_o` of achromatic background as percentage normalised to domain [18, 100] in **'Reference'** domain-range scale. E_o1 : numeric Test illuminance :math:`E_{o1}` in :math:`cd/m^2`. E_o2 : numeric Reference illuminance :math:`E_{o2}` in :math:`cd/m^2`. n : numeric, optional Noise component in fundamental primary system. Returns ------- ndarray Adapted *CIE XYZ_2* tristimulus values of test stimulus. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``XYZ_1`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ | ``Y_o`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``XYZ_2`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`CIETC1-321994b` Examples -------- >>> XYZ_1 = np.array([28.00, 21.26, 5.27]) >>> xy_o1 = np.array([0.4476, 0.4074]) >>> xy_o2 = np.array([0.3127, 0.3290]) >>> Y_o = 20 >>> E_o1 = 1000 >>> E_o2 = 1000 >>> chromatic_adaptation_CIE1994(XYZ_1, xy_o1, xy_o2, Y_o, E_o1, E_o2) ... # doctest: +ELLIPSIS array([ 24.0337952..., 21.1562121..., 17.6430119...]) """ XYZ_1 = to_domain_100(XYZ_1) Y_o = to_domain_100(Y_o) E_o1 = as_float_array(E_o1) E_o2 = as_float_array(E_o2) if np.any(Y_o < 18) or np.any(Y_o > 100): usage_warning(('"Y_o" luminance factor must be in [18, 100] domain, ' 'unpredictable results may occur!')) RGB_1 = XYZ_to_RGB_CIE1994(XYZ_1) xez_1 = intermediate_values(xy_o1) xez_2 = intermediate_values(xy_o2) RGB_o1 = effective_adapting_responses(xez_1, Y_o, E_o1) RGB_o2 = effective_adapting_responses(xez_2, Y_o, E_o2) bRGB_o1 = exponential_factors(RGB_o1) bRGB_o2 = exponential_factors(RGB_o2) K = K_coefficient(xez_1, xez_2, bRGB_o1, bRGB_o2, Y_o, n) RGB_2 = corresponding_colour(RGB_1, xez_1, xez_2, bRGB_o1, bRGB_o2, Y_o, K, n) XYZ_2 = RGB_to_XYZ_CIE1994(RGB_2) return from_range_100(XYZ_2)
def ootf_reverse_BT2100_HLG(F_D, L_B=0, L_W=1000, gamma=None): """ Defines *Recommendation ITU-R BT.2100* *Reference HLG* reverse opto-optical transfer function (OOTF / OOCF). Parameters ---------- F_D : numeric or array_like :math:`F_D` is the luminance of a displayed linear component :math:`{R_D, G_D, or B_D}`, in :math:`cd/m^2`. L_B : numeric, optional :math:`L_B` is the display luminance for black in :math:`cd/m^2`. L_W : numeric, optional :math:`L_W` is nominal peak luminance of the display in :math:`cd/m^2` for achromatic pixels. gamma : numeric, optional System gamma value, 1.2 at the nominal display peak luminance of :math:`1000 cd/m^2`. Returns ------- numeric or ndarray :math:`E` is the signal for each colour component :math:`{R_S, G_S, B_S}` proportional to scene linear light and scaled by camera exposure. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``F_D`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``E`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ References ---------- :cite:`Borer2017a`, :cite:`InternationalTelecommunicationUnion2016a` Examples -------- >>> ootf_reverse_BT2100_HLG(63.095734448019336) # doctest: +ELLIPSIS 0.1000000... """ F_D = np.atleast_1d(to_domain_1(F_D)) if F_D.shape[-1] != 3: usage_warning( '"Recommendation ITU-R BT.2100" "Reference HLG OOTF" uses ' 'RGB Luminance in computations and expects a vector input, thus ' 'the given input array will be stacked to compose a vector for ' 'internal computations but a single component will be output.') R_D = G_D = B_D = F_D else: R_D, G_D, B_D = tsplit(F_D) Y_D = np.sum(BT2100_HLG_WEIGHTS * tstack([R_D, G_D, B_D]), axis=-1) alpha = L_W - L_B beta = L_B if gamma is None: gamma = gamma_function_BT2100_HLG(L_W) R_S = np.where( Y_D == beta, 0.0, (np.abs((Y_D - beta) / alpha) ** ((1 - gamma) / gamma)) * (R_D - beta) / alpha, ) G_S = np.where( Y_D == beta, 0.0, (np.abs((Y_D - beta) / alpha) ** ((1 - gamma) / gamma)) * (G_D - beta) / alpha, ) B_S = np.where( Y_D == beta, 0.0, (np.abs((Y_D - beta) / alpha) ** ((1 - gamma) / gamma)) * (B_D - beta) / alpha, ) if F_D.shape[-1] != 3: return as_float(from_range_1(R_S)) else: RGB_S = tstack([R_S, G_S, B_S]) return from_range_1(RGB_S)
def CCT_to_xy_Hernandez1999(CCT, optimisation_kwargs=None, **kwargs): """ Returns the *CIE xy* chromaticity coordinates from given correlated colour temperature :math:`T_{cp}` using *Hernandez-Andres et al. (1999)* method. Parameters ---------- CCT : numeric or array_like Correlated colour temperature :math:`T_{cp}`. optimisation_kwargs : dict_like, optional Parameters for :func:`scipy.optimize.minimize` definition. Other Parameters ---------------- \\**kwargs : dict, optional Keywords arguments for deprecation management. Returns ------- ndarray *CIE xy* chromaticity coordinates. Warnings -------- *Hernandez-Andres et al. (1999)* method for computing *CIE xy* chromaticity coordinates from given correlated colour temperature is not a bijective function and might produce unexpected results. It is given for consistency with other correlated colour temperature computation methods but should be avoided for practical applications. The current implementation relies on optimization using :func:`scipy.optimize.minimize` definition and thus has reduced precision and poor performance. References ---------- :cite:`Hernandez-Andres1999a` Examples -------- >>> CCT_to_xy_Hernandez1999(6500.7420431786531) # doctest: +ELLIPSIS array([ 0.3127..., 0.329...]) """ optimisation_kwargs = handle_arguments_deprecation( { 'ArgumentRenamed': [['optimisation_parameters', 'optimisation_kwargs']], }, **kwargs).get('optimisation_kwargs', optimisation_kwargs) usage_warning('"Hernandez-Andres et al. (1999)" method for computing ' '"CIE xy" chromaticity coordinates from given correlated ' 'colour temperature is not a bijective function and and' 'might produce unexpected results. It is given for ' 'consistency with other correlated colour temperature ' 'computation methods but should be avoided for practical ' 'applications.') CCT = as_float_array(CCT) shape = list(CCT.shape) CCT = np.atleast_1d(CCT.reshape([-1, 1])) def objective_function(xy, CCT): """ Objective function. """ objective = np.linalg.norm(xy_to_CCT_Hernandez1999(xy) - CCT) return objective optimisation_settings = { 'method': 'Nelder-Mead', 'options': { 'fatol': 1e-10, }, } if optimisation_kwargs is not None: optimisation_settings.update(optimisation_kwargs) CCT = as_float_array([ minimize( objective_function, x0=CCS_ILLUMINANTS['CIE 1931 2 Degree Standard Observer']['D65'], args=(CCT_i, ), **optimisation_settings).x for CCT_i in CCT ]) return as_numeric(CCT.reshape(shape + [2]))
def read_image(path, bit_depth='float32', method='OpenImageIO', **kwargs): """ Reads the image at given path using given method. Parameters ---------- path : unicode Image path. bit_depth : unicode, optional **{'float32', 'uint8', 'uint16', 'float16'}**, Returned image bit depth, for the *Imageio* method, the image data is converted with :func:`colour.io.convert_bit_depth` definition after reading the image, for the *OpenImageIO* method, the bit depth conversion behaviour is driven directly by the library, this definition only converts to the relevant data type after reading. method : unicode, optional **{'OpenImageIO', 'Imageio'}**, Read method, i.e. the image library used for reading images. Other Parameters ---------------- attributes : bool, optional {:func:`colour.io.read_image_OpenImageIO`}, Whether to return the image attributes. Returns ------- ndarray Image as a ndarray. Notes ----- - If the given method is *OpenImageIO* but the library is not available writing will be performed by *Imageio*. - If the given method is *Imageio*, ``kwargs`` is passed directly to the wrapped definition. - For convenience, single channel images are squeezed to 2d arrays. Examples -------- >>> import os >>> import colour >>> path = os.path.join(colour.__path__[0], 'io', 'tests', 'resources', ... 'CMS_Test_Pattern.exr') >>> image = read_image(path) >>> image.shape # doctest: +SKIP (1267, 1274, 3) >>> image.dtype dtype('float32') """ if method.lower() == 'openimageio': # pragma: no cover if not is_openimageio_installed(): usage_warning( '"OpenImageIO" related API features are not available, ' 'switching to "Imageio"!') method = 'Imageio' function = READ_IMAGE_METHODS[method] if method.lower() == 'openimageio': # pragma: no cover kwargs = filter_kwargs(function, **kwargs) return function(path, bit_depth, **kwargs)
def cvd_matrix_Machado2009(deficiency, severity): """ Computes *Machado et al. (2009)* *CVD* matrix for given deficiency and severity using the pre-computed matrices dataset. Parameters ---------- deficiency : unicode {'Protanomaly', 'Deuteranomaly', 'Tritanomaly'} Colour blindness / vision deficiency types : - *Protanomaly* : defective long-wavelength cones (L-cones). The complete absence of L-cones is known as *Protanopia* or *red-dichromacy*. - *Deuteranomaly* : defective medium-wavelength cones (M-cones) with peak of sensitivity moved towards the red sensitive cones. The complete absence of M-cones is known as *Deuteranopia*. - *Tritanomaly* : defective short-wavelength cones (S-cones), an alleviated form of blue-yellow color blindness. The complete absence of S-cones is known as *Tritanopia*. severity : numeric Severity of the colour vision deficiency in domain [0, 1]. Returns ------- ndarray *CVD* matrix. References ---------- :cite:`Colblindorb`, :cite:`Colblindora`, :cite:`Colblindorc`, :cite:`Machado2009` Examples -------- >>> cvd_matrix_Machado2009('Protanomaly', 0.15) # doctest: +ELLIPSIS array([[ 0.7869875..., 0.2694875..., -0.0564735...], [ 0.0431695..., 0.933774 ..., 0.023058 ...], [-0.004238 ..., -0.0024515..., 1.0066895...]]) """ if deficiency.lower() == 'tritanomaly': usage_warning( '"Machado et al. (2009)" simulation of tritanomaly is based on ' 'the shift paradigm as an approximation to the actual phenomenon ' 'and restrain the model from trying to model tritanopia.\n' 'The pre-generated matrices are using a shift value in domain ' '[5, 59] contrary to the domain [0, 20] used for protanomaly and ' 'deuteranomaly simulation.') matrices = CVD_MATRICES_MACHADO2010[deficiency] samples = np.array(sorted(matrices.keys())) index = min(np.searchsorted(samples, severity), len(samples) - 1) a = samples[index] b = samples[min(index + 1, len(samples) - 1)] m1, m2 = matrices[a], matrices[b] if a == b: # 1.0 severity CVD matrix, returning directly. return m1 else: return m1 + (severity - a) * ((m2 - m1) / (b - a))
def CCT_to_xy_McCamy1992( CCT: FloatingOrArrayLike, optimisation_kwargs: Optional[Dict] = None) -> NDArray: """ Return the *CIE xy* chromaticity coordinates from given correlated colour temperature :math:`T_{cp}` using *McCamy (1992)* method. Parameters ---------- CCT Correlated colour temperature :math:`T_{cp}`. optimisation_kwargs Parameters for :func:`scipy.optimize.minimize` definition. Returns ------- :class:`numpy.ndarray` *CIE xy* chromaticity coordinates. Warnings -------- *McCamy (1992)* method for computing *CIE xy* chromaticity coordinates from given correlated colour temperature is not a bijective function and might produce unexpected results. It is given for consistency with other correlated colour temperature computation methods but should be avoided for practical applications. The current implementation relies on optimization using :func:`scipy.optimize.minimize` definition and thus has reduced precision and poor performance. References ---------- :cite:`Wikipedia2001` Examples -------- >>> CCT_to_xy_McCamy1992(6505.0805913074782) # doctest: +ELLIPSIS array([ 0.3127..., 0.329...]) """ usage_warning('"McCamy (1992)" method for computing "CIE xy" ' "chromaticity coordinates from given correlated colour " "temperature is not a bijective function and might produce " "unexpected results. It is given for consistency with other " "correlated colour temperature computation methods but " "should be avoided for practical applications.") CCT = as_float_array(CCT) shape = list(CCT.shape) CCT = np.atleast_1d(CCT.reshape([-1, 1])) def objective_function(xy: ArrayLike, CCT: FloatingOrArrayLike) -> FloatingOrNDArray: """Objective function.""" objective = np.linalg.norm( xy_to_CCT_McCamy1992(xy) - as_float_array(CCT)) return as_float(objective) optimisation_settings = { "method": "Nelder-Mead", "options": { "fatol": 1e-10, }, } if optimisation_kwargs is not None: optimisation_settings.update(optimisation_kwargs) xy = as_float_array([ minimize( objective_function, x0=CCS_ILLUMINANTS["CIE 1931 2 Degree Standard Observer"]["D65"], args=(CCT_i, ), **optimisation_settings, ).x for CCT_i in as_float_array(CCT) ]) return np.reshape(xy, (shape + [2]))