├── .github └── workflows │ └── continuous-integration.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .travis.yml ├── BENCHMARKS.md ├── CHANGES.txt ├── LICENSE.txt ├── MANIFEST.in ├── README.rst ├── colormath ├── __init__.py ├── chromatic_adaptation.py ├── color_appearance_models.py ├── color_constants.py ├── color_conversions.py ├── color_diff.py ├── color_diff_matrix.py ├── color_exceptions.py ├── color_objects.py ├── density.py ├── density_standards.py └── spectral_constants.py ├── doc_src ├── Makefile ├── _build │ └── .GIT_FOO ├── _static │ └── .GIT_FOO ├── _templates │ └── .GIT_FOO ├── color_appearance_models.rst ├── color_objects.rst ├── conf.py ├── conversions.rst ├── delta_e.rst ├── density.rst ├── global.txt ├── illuminants.rst ├── index.rst ├── installation.rst ├── make.bat └── release_notes.rst ├── examples ├── color_appearance_model.py ├── conversions.py ├── delta_e.py ├── delta_e_matrix.py ├── density.py ├── example_config.py └── lab_matrix.csv.bz2 ├── requirements.txt ├── setup.py ├── tests ├── fixtures │ ├── atd.csv │ ├── ciecam02.csv │ ├── hunt.csv │ ├── llab.csv │ ├── nayatani.csv │ └── rlab.csv ├── test_chromatic_adaptation.py ├── test_color_appearance_models.py ├── test_color_conversion.py ├── test_color_diff.py └── test_color_objects.py └── tox.ini /.github/workflows/continuous-integration.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | unix-build: 7 | name: Unix Build 8 | strategy: 9 | matrix: 10 | os: [ubuntu-18.04, macOS-latest] 11 | python-version: [2.7, 3.5, 3.6, 3.7] 12 | runs-on: ${{ matrix.os }} 13 | steps: 14 | - uses: actions/checkout@v1 15 | - name: Set up Python ${{ matrix.python-version }} 16 | uses: actions/setup-python@v1 17 | with: 18 | python-version: ${{ matrix.python-version }} 19 | - name: Install Package Dependencies 20 | run: | 21 | pip install -r requirements.txt 22 | - name: Test with nosetests 23 | run: | 24 | nosetests 25 | windows-build: 26 | name: Windows Build 27 | strategy: 28 | matrix: 29 | os: [windows-2019] 30 | python-version: [2.7, 3.5, 3.6, 3.7] 31 | runs-on: ${{ matrix.os }} 32 | steps: 33 | - uses: actions/checkout@v1 34 | - name: Set up Python ${{ matrix.python-version }} 35 | uses: actions/setup-python@v1 36 | with: 37 | python-version: ${{ matrix.python-version }} 38 | - name: Install Package Dependencies 39 | run: | 40 | pip install -r requirements.txt 41 | shell: cmd 42 | - name: Test with nosetests 43 | run: | 44 | nosetests 45 | shell: cmd 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.swp 3 | .project 4 | .pydevproject 5 | build 6 | dist 7 | MANIFEST 8 | .idea 9 | colormath.egg-info/ 10 | .tox 11 | .DS_Store 12 | doc_src/.doctrees 13 | doc_src/_sources/* 14 | doc_src/_static/* 15 | doc_src/*.html 16 | doc_src/_build/* 17 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://gitlab.com/pycqa/flake8 3 | rev: 3.7.8 4 | hooks: 5 | - id: flake8 6 | args: ['--max-line-length=90'] 7 | - repo: https://github.com/ambv/black 8 | rev: stable 9 | hooks: 10 | - id: black 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | - "3.5" 5 | - "3.6" 6 | - "3.7" 7 | - "3.8" 8 | # command to install dependencies 9 | install: "pip install -r requirements.txt" 10 | # command to run tests 11 | script: nosetests 12 | -------------------------------------------------------------------------------- /BENCHMARKS.md: -------------------------------------------------------------------------------- 1 | This benchmark was executed on a 2012 macbook and used 200k colors from the XKCD color list. Processing of the raw lab values into objects / matrices was not included in the timings. For the full pickled lab matrix see http://lyst-classifiers.s3.amazonaws.com/color/lab-colors.pk 2 | 3 | |method | delta_e | delta_e_matrix| 4 | |:-------|---------:|--------------:| 5 | |cie1976 | 3.256 | 0.018 | 6 | |cie1994 | 3.787 | 0.058 | 7 | |cmc | 4.265 | 0.06 | 8 | |cie2000 | 5.563 | 0.213 | 9 | 10 | On large data-sets the vectorized version is an order of magnitude faster 11 | -------------------------------------------------------------------------------- /CHANGES.txt: -------------------------------------------------------------------------------- 1 | The changelog has been moved to our documentation site: 2 | 3 | http://python-colormath.readthedocs.org/en/latest/release_notes.html 4 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2008-2023, Greg Taylor 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | * Neither the name of the python-colormath nor the names of its 13 | contributors may be used to endorse or promote products derived from this 14 | software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE.txt 2 | include README.rst 3 | recursive-include examples *.txt *.py 4 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Python Color Math Module (colormath) 2 | ==================================== 3 | 4 | **Note: This module is no longer actively maintained.** 5 | 6 | .. start-badges 7 | 8 | |actions| 9 | 10 | .. |actions| image:: https://github.com/gtaylor/python-colormath/workflows/Continuous%20Integration/badge.svg 11 | :target: https://github.com/gtaylor/python-colormath/actions 12 | :alt: Master Build Status 13 | 14 | .. end-badges 15 | 16 | This module implements a large number of different color operations such as 17 | color space conversions, Delta E, and density to spectral. 18 | 19 | Requirements 20 | ------------ 21 | 22 | * numpy 23 | * NetworkX 2.0+ 24 | * Python 2.7 or Python 3.5+ 25 | 26 | Installation 27 | ------------ 28 | 29 | The easiest way to install colormath is via pip/easy_install:: 30 | 31 | $ pip install colormath 32 | 33 | The development dependencies are installed as follows: 34 | 35 | $ pip install 'colormath[development]' 36 | 37 | Documentation 38 | ------------- 39 | 40 | For documentation, see the project webpage at: 41 | 42 | http://python-colormath.readthedocs.org/ 43 | 44 | There are also a lot of useful examples under the examples directory within 45 | this directory. 46 | 47 | Support 48 | ------- 49 | 50 | Head over to https://github.com/gtaylor/python-colormath 51 | and submit an issue if you have any problems or questions. 52 | 53 | Legal Mumbo Jumbo 54 | ----------------- 55 | 56 | Copyright (C) 2008-2023 `Gregory Taylor`_ 57 | 58 | This software is licensed under the BSD License. 59 | 60 | .. _Gregory Taylor: http://gc-taylor.com 61 | -------------------------------------------------------------------------------- /colormath/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | VERSION = "3.0.0" 3 | -------------------------------------------------------------------------------- /colormath/chromatic_adaptation.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import logging 3 | 4 | import numpy 5 | from numpy.linalg import pinv 6 | 7 | from colormath import color_constants 8 | 9 | logger = logging.getLogger(__name__) 10 | 11 | 12 | # noinspection PyPep8Naming 13 | def _get_adaptation_matrix(wp_src, wp_dst, observer, adaptation): 14 | """ 15 | Calculate the correct transformation matrix based on origin and target 16 | illuminants. The observer angle must be the same between illuminants. 17 | 18 | See colormath.color_constants.ADAPTATION_MATRICES for a list of possible 19 | adaptations. 20 | 21 | Detailed conversion documentation is available at: 22 | http://brucelindbloom.com/Eqn_ChromAdapt.html 23 | """ 24 | # Get the appropriate transformation matrix, [MsubA]. 25 | m_sharp = color_constants.ADAPTATION_MATRICES[adaptation] 26 | 27 | # In case the white-points are still input as strings 28 | # Get white-points for illuminant 29 | if isinstance(wp_src, str): 30 | orig_illum = wp_src.lower() 31 | wp_src = color_constants.ILLUMINANTS[observer][orig_illum] 32 | elif hasattr(wp_src, "__iter__"): 33 | wp_src = wp_src 34 | 35 | if isinstance(wp_dst, str): 36 | targ_illum = wp_dst.lower() 37 | wp_dst = color_constants.ILLUMINANTS[observer][targ_illum] 38 | elif hasattr(wp_dst, "__iter__"): 39 | wp_dst = wp_dst 40 | 41 | # Sharpened cone responses ~ rho gamma beta ~ sharpened r g b 42 | rgb_src = numpy.dot(m_sharp, wp_src) 43 | rgb_dst = numpy.dot(m_sharp, wp_dst) 44 | 45 | # Ratio of whitepoint sharpened responses 46 | m_rat = numpy.diag(rgb_dst / rgb_src) 47 | 48 | # Final transformation matrix 49 | m_xfm = numpy.dot(numpy.dot(pinv(m_sharp), m_rat), m_sharp) 50 | 51 | return m_xfm 52 | 53 | 54 | # noinspection PyPep8Naming 55 | def apply_chromatic_adaptation( 56 | val_x, val_y, val_z, orig_illum, targ_illum, observer="2", adaptation="bradford" 57 | ): 58 | """ 59 | Applies a chromatic adaptation matrix to convert XYZ values between 60 | illuminants. It is important to recognize that color transformation results 61 | in color errors, determined by how far the original illuminant is from the 62 | target illuminant. For example, D65 to A could result in very high maximum 63 | deviance. 64 | 65 | An informative article with estimate average Delta E values for each 66 | illuminant conversion may be found at: 67 | 68 | http://brucelindbloom.com/ChromAdaptEval.html 69 | """ 70 | # It's silly to have to do this, but some people may want to call this 71 | # function directly, so we'll protect them from messing up upper/lower case. 72 | adaptation = adaptation.lower() 73 | 74 | # Get white-points for illuminant 75 | if isinstance(orig_illum, str): 76 | orig_illum = orig_illum.lower() 77 | wp_src = color_constants.ILLUMINANTS[observer][orig_illum] 78 | elif hasattr(orig_illum, "__iter__"): 79 | wp_src = orig_illum 80 | 81 | if isinstance(targ_illum, str): 82 | targ_illum = targ_illum.lower() 83 | wp_dst = color_constants.ILLUMINANTS[observer][targ_illum] 84 | elif hasattr(targ_illum, "__iter__"): 85 | wp_dst = targ_illum 86 | 87 | logger.debug(" \\* Applying adaptation matrix: %s", adaptation) 88 | # Retrieve the appropriate transformation matrix from the constants. 89 | transform_matrix = _get_adaptation_matrix(wp_src, wp_dst, observer, adaptation) 90 | 91 | # Stuff the XYZ values into a NumPy matrix for conversion. 92 | XYZ_matrix = numpy.array((val_x, val_y, val_z)) 93 | # Perform the adaptation via matrix multiplication. 94 | result_matrix = numpy.dot(transform_matrix, XYZ_matrix) 95 | 96 | # Return individual X, Y, and Z coordinates. 97 | return result_matrix[0], result_matrix[1], result_matrix[2] 98 | 99 | 100 | # noinspection PyPep8Naming 101 | def apply_chromatic_adaptation_on_color(color, targ_illum, adaptation="bradford"): 102 | """ 103 | Convenience function to apply an adaptation directly to a Color object. 104 | """ 105 | xyz_x = color.xyz_x 106 | xyz_y = color.xyz_y 107 | xyz_z = color.xyz_z 108 | orig_illum = color.illuminant 109 | targ_illum = targ_illum.lower() 110 | observer = color.observer 111 | adaptation = adaptation.lower() 112 | 113 | # Return individual X, Y, and Z coordinates. 114 | color.xyz_x, color.xyz_y, color.xyz_z = apply_chromatic_adaptation( 115 | xyz_x, 116 | xyz_y, 117 | xyz_z, 118 | orig_illum, 119 | targ_illum, 120 | observer=observer, 121 | adaptation=adaptation, 122 | ) 123 | color.set_illuminant(targ_illum) 124 | 125 | return color 126 | -------------------------------------------------------------------------------- /colormath/color_constants.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Contains lookup tables, constants, and things that are generally static 4 | and useful throughout the library. 5 | """ 6 | 7 | import numpy 8 | 9 | # Not sure what these are, they are used in Lab and Luv calculations. 10 | CIE_E = 216.0 / 24389.0 11 | CIE_K = 24389.0 / 27.0 12 | 13 | # Observer Function and Illuminant Data 14 | ILLUMINANTS = { 15 | # 2 Degree Functions 16 | "2": { 17 | "a": (1.09850, 1.00000, 0.35585), 18 | "b": (0.99072, 1.00000, 0.85223), 19 | "c": (0.98074, 1.00000, 1.18232), 20 | "d50": (0.96422, 1.00000, 0.82521), 21 | "d55": (0.95682, 1.00000, 0.92149), 22 | "d65": (0.95047, 1.00000, 1.08883), 23 | "d75": (0.94972, 1.00000, 1.22638), 24 | "e": (1.00000, 1.00000, 1.00000), 25 | "f2": (0.99186, 1.00000, 0.67393), 26 | "f7": (0.95041, 1.00000, 1.08747), 27 | "f11": (1.00962, 1.00000, 0.64350), 28 | }, 29 | # 10 Degree Functions 30 | "10": { 31 | "d50": (0.9672, 1.000, 0.8143), 32 | "d55": (0.958, 1.000, 0.9093), 33 | "d65": (0.9481, 1.000, 1.073), 34 | "d75": (0.94416, 1.000, 1.2064), 35 | }, 36 | } 37 | 38 | OBSERVERS = ILLUMINANTS.keys() 39 | 40 | # Chromatic Adaptation Matrices 41 | # http://brucelindbloom.com/Eqn_ChromAdapt.html 42 | ADAPTATION_MATRICES = { 43 | "xyz_scaling": numpy.array( 44 | ( 45 | (1.00000, 0.00000, 0.00000), 46 | (0.00000, 1.00000, 0.00000), 47 | (0.00000, 0.00000, 1.00000), 48 | ) 49 | ), 50 | "bradford": numpy.array( 51 | ( 52 | (0.8951, 0.2664, -0.1614), 53 | (-0.7502, 1.7135, 0.0367), 54 | (0.0389, -0.0685, 1.0296), 55 | ) 56 | ), 57 | "von_kries": numpy.array( 58 | ( 59 | (0.40024, 0.70760, -0.08081), 60 | (-0.22630, 1.16532, 0.04570), 61 | (0.00000, 0.00000, 0.91822), 62 | ) 63 | ), 64 | } 65 | -------------------------------------------------------------------------------- /colormath/color_diff.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | The functions in this module are used for comparing two LabColor objects 4 | using various Delta E formulas. 5 | """ 6 | 7 | import numpy 8 | 9 | from colormath import color_diff_matrix 10 | 11 | 12 | def _get_lab_color1_vector(color): 13 | """ 14 | Converts an LabColor into a NumPy vector. 15 | 16 | :param LabColor color: 17 | :rtype: numpy.ndarray 18 | """ 19 | if not color.__class__.__name__ == "LabColor": 20 | raise ValueError( 21 | "Delta E functions can only be used with two LabColor objects." 22 | ) 23 | return numpy.array([color.lab_l, color.lab_a, color.lab_b]) 24 | 25 | 26 | def _get_lab_color2_matrix(color): 27 | """ 28 | Converts an LabColor into a NumPy matrix. 29 | 30 | :param LabColor color: 31 | :rtype: numpy.ndarray 32 | """ 33 | if not color.__class__.__name__ == "LabColor": 34 | raise ValueError( 35 | "Delta E functions can only be used with two LabColor objects." 36 | ) 37 | return numpy.array([(color.lab_l, color.lab_a, color.lab_b)]) 38 | 39 | 40 | # noinspection PyPep8Naming 41 | def delta_e_cie1976(color1, color2): 42 | """ 43 | Calculates the Delta E (CIE1976) of two colors. 44 | """ 45 | color1_vector = _get_lab_color1_vector(color1) 46 | color2_matrix = _get_lab_color2_matrix(color2) 47 | delta_e = color_diff_matrix.delta_e_cie1976(color1_vector, color2_matrix)[0] 48 | return delta_e.item() 49 | 50 | 51 | # noinspection PyPep8Naming 52 | def delta_e_cie1994(color1, color2, K_L=1, K_C=1, K_H=1, K_1=0.045, K_2=0.015): 53 | """ 54 | Calculates the Delta E (CIE1994) of two colors. 55 | 56 | K_l: 57 | 0.045 graphic arts 58 | 0.048 textiles 59 | K_2: 60 | 0.015 graphic arts 61 | 0.014 textiles 62 | K_L: 63 | 1 default 64 | 2 textiles 65 | """ 66 | color1_vector = _get_lab_color1_vector(color1) 67 | color2_matrix = _get_lab_color2_matrix(color2) 68 | delta_e = color_diff_matrix.delta_e_cie1994( 69 | color1_vector, color2_matrix, K_L=K_L, K_C=K_C, K_H=K_H, K_1=K_1, K_2=K_2 70 | )[0] 71 | return delta_e.item() 72 | 73 | 74 | # noinspection PyPep8Naming 75 | def delta_e_cie2000(color1, color2, Kl=1, Kc=1, Kh=1): 76 | """ 77 | Calculates the Delta E (CIE2000) of two colors. 78 | """ 79 | color1_vector = _get_lab_color1_vector(color1) 80 | color2_matrix = _get_lab_color2_matrix(color2) 81 | delta_e = color_diff_matrix.delta_e_cie2000( 82 | color1_vector, color2_matrix, Kl=Kl, Kc=Kc, Kh=Kh 83 | )[0] 84 | return delta_e.item() 85 | 86 | 87 | # noinspection PyPep8Naming 88 | def delta_e_cmc(color1, color2, pl=2, pc=1): 89 | """ 90 | Calculates the Delta E (CMC) of two colors. 91 | 92 | CMC values 93 | Acceptability: pl=2, pc=1 94 | Perceptability: pl=1, pc=1 95 | """ 96 | color1_vector = _get_lab_color1_vector(color1) 97 | color2_matrix = _get_lab_color2_matrix(color2) 98 | delta_e = color_diff_matrix.delta_e_cmc(color1_vector, color2_matrix, pl=pl, pc=pc)[ 99 | 0 100 | ] 101 | return delta_e.item() 102 | -------------------------------------------------------------------------------- /colormath/color_diff_matrix.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | This module contains the formulas for comparing Lab values with matrices 4 | and vectors. The benefit of using NumPy's matrix capabilities is speed. These 5 | calls can be used to efficiently compare large volumes of Lab colors. 6 | """ 7 | 8 | import numpy 9 | 10 | 11 | def delta_e_cie1976(lab_color_vector, lab_color_matrix): 12 | """ 13 | Calculates the Delta E (CIE1976) between `lab_color_vector` and all 14 | colors in `lab_color_matrix`. 15 | """ 16 | return numpy.sqrt( 17 | numpy.sum(numpy.power(lab_color_vector - lab_color_matrix, 2), axis=1) 18 | ) 19 | 20 | 21 | # noinspection PyPep8Naming 22 | def delta_e_cie1994( 23 | lab_color_vector, lab_color_matrix, K_L=1, K_C=1, K_H=1, K_1=0.045, K_2=0.015 24 | ): 25 | """ 26 | Calculates the Delta E (CIE1994) of two colors. 27 | 28 | K_l: 29 | 0.045 graphic arts 30 | 0.048 textiles 31 | K_2: 32 | 0.015 graphic arts 33 | 0.014 textiles 34 | K_L: 35 | 1 default 36 | 2 textiles 37 | """ 38 | C_1 = numpy.sqrt(numpy.sum(numpy.power(lab_color_vector[1:], 2))) 39 | C_2 = numpy.sqrt(numpy.sum(numpy.power(lab_color_matrix[:, 1:], 2), axis=1)) 40 | 41 | delta_lab = lab_color_vector - lab_color_matrix 42 | 43 | delta_L = delta_lab[:, 0].copy() 44 | delta_C = C_1 - C_2 45 | delta_lab[:, 0] = delta_C 46 | 47 | delta_H_sq = numpy.sum(numpy.power(delta_lab, 2) * numpy.array([-1, 1, 1]), axis=1) 48 | # noinspection PyArgumentList 49 | delta_H = numpy.sqrt(delta_H_sq.clip(min=0)) 50 | 51 | S_L = 1 52 | S_C = 1 + K_1 * C_1 53 | S_H = 1 + K_2 * C_1 54 | 55 | LCH = numpy.vstack([delta_L, delta_C, delta_H]) 56 | params = numpy.array([[K_L * S_L], [K_C * S_C], [K_H * S_H]]) 57 | 58 | return numpy.sqrt(numpy.sum(numpy.power(LCH / params, 2), axis=0)) 59 | 60 | 61 | # noinspection PyPep8Naming 62 | def delta_e_cmc(lab_color_vector, lab_color_matrix, pl=2, pc=1): 63 | """ 64 | Calculates the Delta E (CIE1994) of two colors. 65 | 66 | CMC values 67 | Acceptability: pl=2, pc=1 68 | Perceptability: pl=1, pc=1 69 | """ 70 | L, a, b = lab_color_vector 71 | 72 | C_1 = numpy.sqrt(numpy.sum(numpy.power(lab_color_vector[1:], 2))) 73 | C_2 = numpy.sqrt(numpy.sum(numpy.power(lab_color_matrix[:, 1:], 2), axis=1)) 74 | 75 | delta_lab = lab_color_vector - lab_color_matrix 76 | 77 | delta_L = delta_lab[:, 0].copy() 78 | delta_C = C_1 - C_2 79 | delta_lab[:, 0] = delta_C 80 | 81 | H_1 = numpy.degrees(numpy.arctan2(b, a)) 82 | 83 | if H_1 < 0: 84 | H_1 += 360 85 | 86 | F = numpy.sqrt(numpy.power(C_1, 4) / (numpy.power(C_1, 4) + 1900.0)) 87 | 88 | # noinspection PyChainedComparisons 89 | if 164 <= H_1 and H_1 <= 345: 90 | T = 0.56 + abs(0.2 * numpy.cos(numpy.radians(H_1 + 168))) 91 | else: 92 | T = 0.36 + abs(0.4 * numpy.cos(numpy.radians(H_1 + 35))) 93 | 94 | if L < 16: 95 | S_L = 0.511 96 | else: 97 | S_L = (0.040975 * L) / (1 + 0.01765 * L) 98 | 99 | S_C = ((0.0638 * C_1) / (1 + 0.0131 * C_1)) + 0.638 100 | S_H = S_C * (F * T + 1 - F) 101 | 102 | delta_C = C_1 - C_2 103 | 104 | delta_H_sq = numpy.sum(numpy.power(delta_lab, 2) * numpy.array([-1, 1, 1]), axis=1) 105 | # noinspection PyArgumentList 106 | delta_H = numpy.sqrt(delta_H_sq.clip(min=0)) 107 | 108 | LCH = numpy.vstack([delta_L, delta_C, delta_H]) 109 | params = numpy.array([[pl * S_L], [pc * S_C], [S_H]]) 110 | 111 | return numpy.sqrt(numpy.sum(numpy.power(LCH / params, 2), axis=0)) 112 | 113 | 114 | # noinspection PyPep8Naming 115 | def delta_e_cie2000(lab_color_vector, lab_color_matrix, Kl=1, Kc=1, Kh=1): 116 | """ 117 | Calculates the Delta E (CIE2000) of two colors. 118 | """ 119 | L, a, b = lab_color_vector 120 | 121 | avg_Lp = (L + lab_color_matrix[:, 0]) / 2.0 122 | 123 | C1 = numpy.sqrt(numpy.sum(numpy.power(lab_color_vector[1:], 2))) 124 | C2 = numpy.sqrt(numpy.sum(numpy.power(lab_color_matrix[:, 1:], 2), axis=1)) 125 | 126 | avg_C1_C2 = (C1 + C2) / 2.0 127 | 128 | G = 0.5 * ( 129 | 1 130 | - numpy.sqrt( 131 | numpy.power(avg_C1_C2, 7.0) 132 | / (numpy.power(avg_C1_C2, 7.0) + numpy.power(25.0, 7.0)) 133 | ) 134 | ) 135 | 136 | a1p = (1.0 + G) * a 137 | a2p = (1.0 + G) * lab_color_matrix[:, 1] 138 | 139 | C1p = numpy.sqrt(numpy.power(a1p, 2) + numpy.power(b, 2)) 140 | C2p = numpy.sqrt(numpy.power(a2p, 2) + numpy.power(lab_color_matrix[:, 2], 2)) 141 | 142 | avg_C1p_C2p = (C1p + C2p) / 2.0 143 | 144 | h1p = numpy.degrees(numpy.arctan2(b, a1p)) 145 | h1p += (h1p < 0) * 360 146 | 147 | h2p = numpy.degrees(numpy.arctan2(lab_color_matrix[:, 2], a2p)) 148 | h2p += (h2p < 0) * 360 149 | 150 | avg_Hp = (((numpy.fabs(h1p - h2p) > 180) * 360) + h1p + h2p) / 2.0 151 | 152 | T = ( 153 | 1 154 | - 0.17 * numpy.cos(numpy.radians(avg_Hp - 30)) 155 | + 0.24 * numpy.cos(numpy.radians(2 * avg_Hp)) 156 | + 0.32 * numpy.cos(numpy.radians(3 * avg_Hp + 6)) 157 | - 0.2 * numpy.cos(numpy.radians(4 * avg_Hp - 63)) 158 | ) 159 | 160 | diff_h2p_h1p = h2p - h1p 161 | delta_hp = diff_h2p_h1p + (numpy.fabs(diff_h2p_h1p) > 180) * 360 162 | delta_hp -= (h2p > h1p) * 720 163 | 164 | delta_Lp = lab_color_matrix[:, 0] - L 165 | delta_Cp = C2p - C1p 166 | delta_Hp = 2 * numpy.sqrt(C2p * C1p) * numpy.sin(numpy.radians(delta_hp) / 2.0) 167 | 168 | S_L = 1 + ( 169 | (0.015 * numpy.power(avg_Lp - 50, 2)) 170 | / numpy.sqrt(20 + numpy.power(avg_Lp - 50, 2.0)) 171 | ) 172 | S_C = 1 + 0.045 * avg_C1p_C2p 173 | S_H = 1 + 0.015 * avg_C1p_C2p * T 174 | 175 | delta_ro = 30 * numpy.exp(-(numpy.power(((avg_Hp - 275) / 25), 2.0))) 176 | R_C = numpy.sqrt( 177 | (numpy.power(avg_C1p_C2p, 7.0)) 178 | / (numpy.power(avg_C1p_C2p, 7.0) + numpy.power(25.0, 7.0)) 179 | ) 180 | R_T = -2 * R_C * numpy.sin(2 * numpy.radians(delta_ro)) 181 | 182 | return numpy.sqrt( 183 | numpy.power(delta_Lp / (S_L * Kl), 2) 184 | + numpy.power(delta_Cp / (S_C * Kc), 2) 185 | + numpy.power(delta_Hp / (S_H * Kh), 2) 186 | + R_T * (delta_Cp / (S_C * Kc)) * (delta_Hp / (S_H * Kh)) 187 | ) 188 | -------------------------------------------------------------------------------- /colormath/color_exceptions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | This module contains exceptions for use throughout the L11 Colorlib. 4 | """ 5 | 6 | 7 | class ColorMathException(Exception): 8 | """ 9 | Base exception for all colormath exceptions. 10 | """ 11 | 12 | pass 13 | 14 | 15 | class UndefinedConversionError(ColorMathException): 16 | """ 17 | Raised when the user asks for a color space conversion that does not exist. 18 | """ 19 | 20 | def __init__(self, cobj, cs_to): 21 | super(UndefinedConversionError, self).__init__(cobj, cs_to) 22 | self.message = "Conversion from %s to %s is not defined." % (cobj, cs_to) 23 | 24 | 25 | class InvalidIlluminantError(ColorMathException): 26 | """ 27 | Raised when an invalid illuminant is set on a ColorObj. 28 | """ 29 | 30 | def __init__(self, illuminant): 31 | super(InvalidIlluminantError, self).__init__(illuminant) 32 | self.message = "Invalid illuminant specified: %s" % illuminant 33 | 34 | 35 | class InvalidObserverError(ColorMathException): 36 | """ 37 | Raised when an invalid observer is set on a ColorObj. 38 | """ 39 | 40 | def __init__(self, cobj): 41 | super(InvalidObserverError, self).__init__(cobj) 42 | self.message = "Invalid observer angle specified: %s" % cobj.observer 43 | -------------------------------------------------------------------------------- /colormath/color_objects.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | This module contains classes to represent various color spaces. 4 | """ 5 | 6 | import logging 7 | import math 8 | 9 | import numpy 10 | 11 | from colormath import color_constants 12 | from colormath import density 13 | from colormath.chromatic_adaptation import apply_chromatic_adaptation_on_color 14 | from colormath.color_exceptions import InvalidObserverError, InvalidIlluminantError 15 | 16 | logger = logging.getLogger(__name__) 17 | 18 | 19 | class ColorBase(object): 20 | """ 21 | A base class holding some common methods and values. 22 | """ 23 | 24 | # Attribute names containing color data on the sub-class. For example, 25 | # sRGBColor would be ['rgb_r', 'rgb_g', 'rgb_b'] 26 | VALUES = [] 27 | # If this object as converted such that its values passed through an 28 | # RGB colorspace, this is set to the class for said RGB color space. 29 | # Allows reversing conversions automatically and accurately. 30 | _through_rgb_type = None 31 | 32 | def get_value_tuple(self): 33 | """ 34 | Returns a tuple of the color's values (in order). For example, 35 | an LabColor object will return (lab_l, lab_a, lab_b), where each 36 | member of the tuple is the float value for said variable. 37 | """ 38 | retval = tuple() 39 | for val in self.VALUES: 40 | retval += (getattr(self, val),) 41 | return retval 42 | 43 | def __str__(self): 44 | """ 45 | String representation of the color. 46 | """ 47 | retval = self.__class__.__name__ + " (" 48 | for val in self.VALUES: 49 | value = getattr(self, val, None) 50 | if value is not None: 51 | retval += "%s:%.4f " % (val, getattr(self, val)) 52 | if hasattr(self, "observer"): 53 | retval += "observer:" + self.observer 54 | if hasattr(self, "illuminant"): 55 | retval += " illuminant:" + self.illuminant 56 | return retval.strip() + ")" 57 | 58 | def __repr__(self): 59 | """ 60 | Evaluable string representation of the object. 61 | """ 62 | retval = self.__class__.__name__ + "(" 63 | attributes = [(attr, getattr(self, attr)) for attr in self.VALUES] 64 | values = [x + "=" + repr(y) for x, y in attributes] 65 | retval += ", ".join(values) 66 | if hasattr(self, "observer"): 67 | retval += ", observer='" + self.observer + "'" 68 | if hasattr(self, "illuminant"): 69 | retval += ", illuminant='" + self.illuminant + "'" 70 | return retval + ")" 71 | 72 | 73 | class IlluminantMixin(object): 74 | """ 75 | Color spaces that have a notion of an illuminant should inherit this. 76 | """ 77 | 78 | # noinspection PyAttributeOutsideInit 79 | def set_observer(self, observer): 80 | """ 81 | Validates and sets the color's observer angle. 82 | 83 | .. note:: This only changes the observer angle value. It does no conversion 84 | of the color's coordinates. 85 | 86 | :param str observer: One of '2' or '10'. 87 | """ 88 | observer = str(observer) 89 | if observer not in color_constants.OBSERVERS: 90 | raise InvalidObserverError(self) 91 | self.observer = observer 92 | 93 | # noinspection PyAttributeOutsideInit 94 | def set_illuminant(self, illuminant): 95 | """ 96 | Validates and sets the color's illuminant. 97 | 98 | .. note:: This only changes the illuminant. It does no conversion 99 | of the color's coordinates. For this, you'll want to refer to 100 | :py:meth:`XYZColor.apply_adaptation \ 101 | `. 102 | 103 | .. tip:: Call this after setting your observer. 104 | 105 | :param str illuminant: One of the various illuminants. 106 | """ 107 | illuminant = illuminant.lower() 108 | if illuminant not in color_constants.ILLUMINANTS[self.observer]: 109 | raise InvalidIlluminantError(illuminant) 110 | self.illuminant = illuminant 111 | 112 | def get_illuminant_xyz(self, observer=None, illuminant=None): 113 | """ 114 | :param str observer: Get the XYZ values for another observer angle. Must 115 | be either '2' or '10'. 116 | :param str illuminant: Get the XYZ values for another illuminant. 117 | :returns: the color's illuminant's XYZ values. 118 | """ 119 | try: 120 | if observer is None: 121 | observer = self.observer 122 | 123 | illums_observer = color_constants.ILLUMINANTS[observer] 124 | except KeyError: 125 | raise InvalidObserverError(self) 126 | 127 | try: 128 | if illuminant is None: 129 | illuminant = self.illuminant 130 | 131 | illum_xyz = illums_observer[illuminant] 132 | except (KeyError, AttributeError): 133 | raise InvalidIlluminantError(illuminant) 134 | 135 | return {"X": illum_xyz[0], "Y": illum_xyz[1], "Z": illum_xyz[2]} 136 | 137 | 138 | class SpectralColor(IlluminantMixin, ColorBase): 139 | """ 140 | A SpectralColor represents a spectral power distribution, as read by 141 | a spectrophotometer. Our current implementation has wavelength intervals 142 | of 10nm, starting at 340nm and ending at 830nm. 143 | 144 | Spectral colors are the lowest level, most "raw" measurement of color. 145 | You may convert spectral colors to any other color space, but you can't 146 | convert any other color space back to spectral. 147 | 148 | See `Spectral power distribution \ 149 | `_ 150 | on Wikipedia for some higher level details on how these work. 151 | """ 152 | 153 | VALUES = [ 154 | "spec_340nm", 155 | "spec_350nm", 156 | "spec_360nm", 157 | "spec_370nm", 158 | "spec_380nm", 159 | "spec_390nm", 160 | "spec_400nm", 161 | "spec_410nm", 162 | "spec_420nm", 163 | "spec_430nm", 164 | "spec_440nm", 165 | "spec_450nm", 166 | "spec_460nm", 167 | "spec_470nm", 168 | "spec_480nm", 169 | "spec_490nm", 170 | "spec_500nm", 171 | "spec_510nm", 172 | "spec_520nm", 173 | "spec_530nm", 174 | "spec_540nm", 175 | "spec_550nm", 176 | "spec_560nm", 177 | "spec_570nm", 178 | "spec_580nm", 179 | "spec_590nm", 180 | "spec_600nm", 181 | "spec_610nm", 182 | "spec_620nm", 183 | "spec_630nm", 184 | "spec_640nm", 185 | "spec_650nm", 186 | "spec_660nm", 187 | "spec_670nm", 188 | "spec_680nm", 189 | "spec_690nm", 190 | "spec_700nm", 191 | "spec_710nm", 192 | "spec_720nm", 193 | "spec_730nm", 194 | "spec_740nm", 195 | "spec_750nm", 196 | "spec_760nm", 197 | "spec_770nm", 198 | "spec_780nm", 199 | "spec_790nm", 200 | "spec_800nm", 201 | "spec_810nm", 202 | "spec_820nm", 203 | "spec_830nm", 204 | ] 205 | 206 | def __init__( 207 | self, 208 | spec_340nm=0.0, 209 | spec_350nm=0.0, 210 | spec_360nm=0.0, 211 | spec_370nm=0.0, 212 | spec_380nm=0.0, 213 | spec_390nm=0.0, 214 | spec_400nm=0.0, 215 | spec_410nm=0.0, 216 | spec_420nm=0.0, 217 | spec_430nm=0.0, 218 | spec_440nm=0.0, 219 | spec_450nm=0.0, 220 | spec_460nm=0.0, 221 | spec_470nm=0.0, 222 | spec_480nm=0.0, 223 | spec_490nm=0.0, 224 | spec_500nm=0.0, 225 | spec_510nm=0.0, 226 | spec_520nm=0.0, 227 | spec_530nm=0.0, 228 | spec_540nm=0.0, 229 | spec_550nm=0.0, 230 | spec_560nm=0.0, 231 | spec_570nm=0.0, 232 | spec_580nm=0.0, 233 | spec_590nm=0.0, 234 | spec_600nm=0.0, 235 | spec_610nm=0.0, 236 | spec_620nm=0.0, 237 | spec_630nm=0.0, 238 | spec_640nm=0.0, 239 | spec_650nm=0.0, 240 | spec_660nm=0.0, 241 | spec_670nm=0.0, 242 | spec_680nm=0.0, 243 | spec_690nm=0.0, 244 | spec_700nm=0.0, 245 | spec_710nm=0.0, 246 | spec_720nm=0.0, 247 | spec_730nm=0.0, 248 | spec_740nm=0.0, 249 | spec_750nm=0.0, 250 | spec_760nm=0.0, 251 | spec_770nm=0.0, 252 | spec_780nm=0.0, 253 | spec_790nm=0.0, 254 | spec_800nm=0.0, 255 | spec_810nm=0.0, 256 | spec_820nm=0.0, 257 | spec_830nm=0.0, 258 | observer="2", 259 | illuminant="d50", 260 | ): 261 | """ 262 | :keyword str observer: Observer angle. Either ``'2'`` or ``'10'`` degrees. 263 | :keyword str illuminant: See :doc:`illuminants` for valid values. 264 | """ 265 | 266 | super(SpectralColor, self).__init__() 267 | # Spectral fields 268 | self.spec_340nm = float(spec_340nm) 269 | self.spec_350nm = float(spec_350nm) 270 | self.spec_360nm = float(spec_360nm) 271 | self.spec_370nm = float(spec_370nm) 272 | # begin Blue wavelengths 273 | self.spec_380nm = float(spec_380nm) 274 | self.spec_390nm = float(spec_390nm) 275 | self.spec_400nm = float(spec_400nm) 276 | self.spec_410nm = float(spec_410nm) 277 | self.spec_420nm = float(spec_420nm) 278 | self.spec_430nm = float(spec_430nm) 279 | self.spec_440nm = float(spec_440nm) 280 | self.spec_450nm = float(spec_450nm) 281 | self.spec_460nm = float(spec_460nm) 282 | self.spec_470nm = float(spec_470nm) 283 | self.spec_480nm = float(spec_480nm) 284 | self.spec_490nm = float(spec_490nm) 285 | # end Blue wavelengths 286 | # start Green wavelengths 287 | self.spec_500nm = float(spec_500nm) 288 | self.spec_510nm = float(spec_510nm) 289 | self.spec_520nm = float(spec_520nm) 290 | self.spec_530nm = float(spec_530nm) 291 | self.spec_540nm = float(spec_540nm) 292 | self.spec_550nm = float(spec_550nm) 293 | self.spec_560nm = float(spec_560nm) 294 | self.spec_570nm = float(spec_570nm) 295 | self.spec_580nm = float(spec_580nm) 296 | self.spec_590nm = float(spec_590nm) 297 | self.spec_600nm = float(spec_600nm) 298 | self.spec_610nm = float(spec_610nm) 299 | # end Green wavelengths 300 | # start Red wavelengths 301 | self.spec_620nm = float(spec_620nm) 302 | self.spec_630nm = float(spec_630nm) 303 | self.spec_640nm = float(spec_640nm) 304 | self.spec_650nm = float(spec_650nm) 305 | self.spec_660nm = float(spec_660nm) 306 | self.spec_670nm = float(spec_670nm) 307 | self.spec_680nm = float(spec_680nm) 308 | self.spec_690nm = float(spec_690nm) 309 | self.spec_700nm = float(spec_700nm) 310 | self.spec_710nm = float(spec_710nm) 311 | self.spec_720nm = float(spec_720nm) 312 | # end Red wavelengths 313 | self.spec_730nm = float(spec_730nm) 314 | self.spec_740nm = float(spec_740nm) 315 | self.spec_750nm = float(spec_750nm) 316 | self.spec_760nm = float(spec_760nm) 317 | self.spec_770nm = float(spec_770nm) 318 | self.spec_780nm = float(spec_780nm) 319 | self.spec_790nm = float(spec_790nm) 320 | self.spec_800nm = float(spec_800nm) 321 | self.spec_810nm = float(spec_810nm) 322 | self.spec_820nm = float(spec_820nm) 323 | self.spec_830nm = float(spec_830nm) 324 | 325 | #: The color's observer angle. Set with :py:meth:`set_observer`. 326 | self.observer = None 327 | #: The color's illuminant. Set with :py:meth:`set_illuminant`. 328 | self.illuminant = None 329 | 330 | self.set_observer(observer) 331 | self.set_illuminant(illuminant) 332 | 333 | def get_numpy_array(self): 334 | """ 335 | Dump this color into NumPy array. 336 | """ 337 | # This holds the obect's spectral data, and will be passed to 338 | # numpy.array() to create a numpy array (matrix) for the matrix math 339 | # that will be done during the conversion to XYZ. 340 | values = [] 341 | 342 | # Use the required value list to build this dynamically. Default to 343 | # 0.0, since that ultimately won't affect the outcome due to the math 344 | # involved. 345 | for val in self.VALUES: 346 | values.append(getattr(self, val, 0.0)) 347 | 348 | # Create and the actual numpy array/matrix from the spectral list. 349 | color_array = numpy.array([values]) 350 | return color_array 351 | 352 | def calc_density(self, density_standard=None): 353 | """ 354 | Calculates the density of the SpectralColor. By default, Status T 355 | density is used, and the correct density distribution (Red, Green, 356 | or Blue) is chosen by comparing the Red, Green, and Blue components of 357 | the spectral sample (the values being red in via "filters"). 358 | """ 359 | if density_standard is not None: 360 | return density.ansi_density(self, density_standard) 361 | else: 362 | return density.auto_density(self) 363 | 364 | 365 | class LabColor(IlluminantMixin, ColorBase): 366 | """ 367 | Represents a CIE Lab color. For more information on CIE Lab, 368 | see `Lab color space `_ on 369 | Wikipedia. 370 | """ 371 | 372 | VALUES = ["lab_l", "lab_a", "lab_b"] 373 | 374 | def __init__(self, lab_l, lab_a, lab_b, observer="2", illuminant="d50"): 375 | """ 376 | :param float lab_l: L coordinate. 377 | :param float lab_a: a coordinate. 378 | :param float lab_b: b coordinate. 379 | :keyword str observer: Observer angle. Either ``'2'`` or ``'10'`` degrees. 380 | :keyword str illuminant: See :doc:`illuminants` for valid values. 381 | """ 382 | super(LabColor, self).__init__() 383 | #: L coordinate 384 | self.lab_l = float(lab_l) 385 | #: a coordinate 386 | self.lab_a = float(lab_a) 387 | #: b coordinate 388 | self.lab_b = float(lab_b) 389 | 390 | #: The color's observer angle. Set with :py:meth:`set_observer`. 391 | self.observer = None 392 | #: The color's illuminant. Set with :py:meth:`set_illuminant`. 393 | self.illuminant = None 394 | 395 | self.set_observer(observer) 396 | self.set_illuminant(illuminant) 397 | 398 | 399 | class LCHabColor(IlluminantMixin, ColorBase): 400 | """ 401 | Represents an CIE LCH color that was converted to LCH by passing through 402 | CIE Lab. This differs from :py:class:`LCHuvColor`, which was converted to 403 | LCH through CIE Luv. 404 | 405 | See `Introduction to Colour Spaces \ 406 | `_ by Phil Cruse for an 407 | illustration of how CIE LCH differs from CIE Lab. 408 | """ 409 | 410 | VALUES = ["lch_l", "lch_c", "lch_h"] 411 | 412 | def __init__(self, lch_l, lch_c, lch_h, observer="2", illuminant="d50"): 413 | """ 414 | :param float lch_l: L coordinate. 415 | :param float lch_c: C coordinate. 416 | :param float lch_h: H coordinate. 417 | :keyword str observer: Observer angle. Either ``'2'`` or ``'10'`` degrees. 418 | :keyword str illuminant: See :doc:`illuminants` for valid values. 419 | """ 420 | super(LCHabColor, self).__init__() 421 | #: L coordinate 422 | self.lch_l = float(lch_l) 423 | #: C coordinate 424 | self.lch_c = float(lch_c) 425 | #: H coordinate 426 | self.lch_h = float(lch_h) 427 | 428 | #: The color's observer angle. Set with :py:meth:`set_observer`. 429 | self.observer = None 430 | #: The color's illuminant. Set with :py:meth:`set_illuminant`. 431 | self.illuminant = None 432 | 433 | self.set_observer(observer) 434 | self.set_illuminant(illuminant) 435 | 436 | 437 | class LCHuvColor(IlluminantMixin, ColorBase): 438 | """ 439 | Represents an CIE LCH color that was converted to LCH by passing through 440 | CIE Luv. This differs from :py:class:`LCHabColor`, which was converted to 441 | LCH through CIE Lab. 442 | 443 | See `Introduction to Colour Spaces \ 444 | `_ by Phil Cruse for an 445 | illustration of how CIE LCH differs from CIE Lab. 446 | """ 447 | 448 | VALUES = ["lch_l", "lch_c", "lch_h"] 449 | 450 | def __init__(self, lch_l, lch_c, lch_h, observer="2", illuminant="d50"): 451 | """ 452 | :param float lch_l: L coordinate. 453 | :param float lch_c: C coordinate. 454 | :param float lch_h: H coordinate. 455 | :keyword str observer: Observer angle. Either ``'2'`` or ``'10'`` degrees. 456 | :keyword str illuminant: See :doc:`illuminants` for valid values. 457 | """ 458 | super(LCHuvColor, self).__init__() 459 | #: L coordinate 460 | self.lch_l = float(lch_l) 461 | #: C coordinate 462 | self.lch_c = float(lch_c) 463 | #: H coordinate 464 | self.lch_h = float(lch_h) 465 | 466 | #: The color's observer angle. Set with :py:meth:`set_observer`. 467 | self.observer = None 468 | #: The color's illuminant. Set with :py:meth:`set_illuminant`. 469 | self.illuminant = None 470 | 471 | self.set_observer(observer) 472 | self.set_illuminant(illuminant) 473 | 474 | 475 | class LuvColor(IlluminantMixin, ColorBase): 476 | """ 477 | Represents an Luv color. 478 | """ 479 | 480 | VALUES = ["luv_l", "luv_u", "luv_v"] 481 | 482 | def __init__(self, luv_l, luv_u, luv_v, observer="2", illuminant="d50"): 483 | """ 484 | :param float luv_l: L coordinate. 485 | :param float luv_u: u coordinate. 486 | :param float luv_v: v coordinate. 487 | :keyword str observer: Observer angle. Either ``'2'`` or ``'10'`` degrees. 488 | :keyword str illuminant: See :doc:`illuminants` for valid values. 489 | """ 490 | super(LuvColor, self).__init__() 491 | #: L coordinate 492 | self.luv_l = float(luv_l) 493 | #: u coordinate 494 | self.luv_u = float(luv_u) 495 | #: v coordinate 496 | self.luv_v = float(luv_v) 497 | 498 | #: The color's observer angle. Set with :py:meth:`set_observer`. 499 | self.observer = None 500 | #: The color's illuminant. Set with :py:meth:`set_illuminant`. 501 | self.illuminant = None 502 | 503 | self.set_observer(observer) 504 | self.set_illuminant(illuminant) 505 | 506 | 507 | class XYZColor(IlluminantMixin, ColorBase): 508 | """ 509 | Represents an XYZ color. 510 | """ 511 | 512 | VALUES = ["xyz_x", "xyz_y", "xyz_z"] 513 | 514 | def __init__(self, xyz_x, xyz_y, xyz_z, observer="2", illuminant="d50"): 515 | """ 516 | :param float xyz_x: X coordinate. 517 | :param float xyz_y: Y coordinate. 518 | :param float xyz_z: Z coordinate. 519 | :keyword str observer: Observer angle. Either ``'2'`` or ``'10'`` degrees. 520 | :keyword str illuminant: See :doc:`illuminants` for valid values. 521 | """ 522 | super(XYZColor, self).__init__() 523 | #: X coordinate 524 | self.xyz_x = float(xyz_x) 525 | #: Y coordinate 526 | self.xyz_y = float(xyz_y) 527 | #: Z coordinate 528 | self.xyz_z = float(xyz_z) 529 | 530 | #: The color's observer angle. Set with :py:meth:`set_observer`. 531 | self.observer = None 532 | #: The color's illuminant. Set with :py:meth:`set_illuminant`. 533 | self.illuminant = None 534 | 535 | self.set_observer(observer) 536 | self.set_illuminant(illuminant) 537 | 538 | def apply_adaptation(self, target_illuminant, adaptation="bradford"): 539 | """ 540 | This applies an adaptation matrix to change the XYZ color's illuminant. 541 | You'll most likely only need this during RGB conversions. 542 | """ 543 | logger.debug(" \\- Original illuminant: %s", self.illuminant) 544 | logger.debug(" \\- Target illuminant: %s", target_illuminant) 545 | 546 | # If the XYZ values were taken with a different reference white than the 547 | # native reference white of the target RGB space, a transformation matrix 548 | # must be applied. 549 | if self.illuminant != target_illuminant: 550 | logger.debug( 551 | " \\* Applying transformation from %s to %s ", 552 | self.illuminant, 553 | target_illuminant, 554 | ) 555 | # Sets the adjusted XYZ values, and the new illuminant. 556 | apply_chromatic_adaptation_on_color( 557 | color=self, targ_illum=target_illuminant, adaptation=adaptation 558 | ) 559 | 560 | 561 | # noinspection PyPep8Naming 562 | class xyYColor(IlluminantMixin, ColorBase): 563 | """ 564 | Represents an xyY color. 565 | """ 566 | 567 | VALUES = ["xyy_x", "xyy_y", "xyy_Y"] 568 | 569 | def __init__(self, xyy_x, xyy_y, xyy_Y, observer="2", illuminant="d50"): 570 | """ 571 | :param float xyy_x: x coordinate. 572 | :param float xyy_y: y coordinate. 573 | :param float xyy_Y: Y coordinate. 574 | :keyword str observer: Observer angle. Either ``'2'`` or ``'10'`` degrees. 575 | :keyword str illuminant: See :doc:`illuminants` for valid values. 576 | """ 577 | super(xyYColor, self).__init__() 578 | #: x coordinate 579 | self.xyy_x = float(xyy_x) 580 | #: y coordinate 581 | self.xyy_y = float(xyy_y) 582 | #: Y coordinate 583 | self.xyy_Y = float(xyy_Y) 584 | 585 | #: The color's observer angle. Set with :py:meth:`set_observer`. 586 | self.observer = None 587 | #: The color's illuminant. Set with :py:meth:`set_illuminant`. 588 | self.illuminant = None 589 | 590 | self.set_observer(observer) 591 | self.set_illuminant(illuminant) 592 | 593 | 594 | class BaseRGBColor(ColorBase): 595 | """ 596 | Base class for all RGB color spaces. 597 | 598 | .. warning:: Do not use this class directly! 599 | """ 600 | 601 | VALUES = ["rgb_r", "rgb_g", "rgb_b"] 602 | 603 | def __init__(self, rgb_r, rgb_g, rgb_b, is_upscaled=False): 604 | """ 605 | :param float rgb_r: R coordinate. 0.0-1.0, or 0-255 if is_upscaled=True. 606 | :param float rgb_g: G coordinate. 0.0-1.0, or 0-255 if is_upscaled=True. 607 | :param float rgb_b: B coordinate. 0.0-1.0, or 0-255 if is_upscaled=True. 608 | :keyword bool is_upscaled: If False, RGB coordinate values are 609 | between 0.0 and 1.0. If True, RGB values are between 0 and 255. 610 | """ 611 | super(BaseRGBColor, self).__init__() 612 | if is_upscaled: 613 | self.rgb_r = rgb_r / 255.0 614 | self.rgb_g = rgb_g / 255.0 615 | self.rgb_b = rgb_b / 255.0 616 | else: 617 | self.rgb_r = float(rgb_r) 618 | self.rgb_g = float(rgb_g) 619 | self.rgb_b = float(rgb_b) 620 | self.is_upscaled = is_upscaled 621 | 622 | def _clamp_rgb_coordinate(self, coord): 623 | """ 624 | Clamps an RGB coordinate, taking into account whether or not the 625 | color is upscaled or not. 626 | 627 | :param float coord: The coordinate value. 628 | :rtype: float 629 | :returns: The clamped value. 630 | """ 631 | if not self.is_upscaled: 632 | return min(max(coord, 0.0), 1.0) 633 | else: 634 | return min(max(coord, 0.0), 255.0) 635 | 636 | @property 637 | def clamped_rgb_r(self): 638 | """ 639 | The clamped (0.0-1.0) R value. 640 | """ 641 | return self._clamp_rgb_coordinate(self.rgb_r) 642 | 643 | @property 644 | def clamped_rgb_g(self): 645 | """ 646 | The clamped (0.0-1.0) G value. 647 | """ 648 | return self._clamp_rgb_coordinate(self.rgb_g) 649 | 650 | @property 651 | def clamped_rgb_b(self): 652 | """ 653 | The clamped (0.0-1.0) B value. 654 | """ 655 | return self._clamp_rgb_coordinate(self.rgb_b) 656 | 657 | def get_upscaled_value_tuple(self): 658 | """ 659 | Scales an RGB color object from decimal 0.0-1.0 to int 0-255. 660 | """ 661 | # Scale up to 0-255 values. 662 | rgb_r = int(math.floor(0.5 + self.rgb_r * 255)) 663 | rgb_g = int(math.floor(0.5 + self.rgb_g * 255)) 664 | rgb_b = int(math.floor(0.5 + self.rgb_b * 255)) 665 | 666 | return rgb_r, rgb_g, rgb_b 667 | 668 | def get_rgb_hex(self): 669 | """ 670 | Converts the RGB value to a hex value in the form of: #RRGGBB 671 | 672 | :rtype: str 673 | """ 674 | rgb_r, rgb_g, rgb_b = self.get_upscaled_value_tuple() 675 | return "#%02x%02x%02x" % (rgb_r, rgb_g, rgb_b) 676 | 677 | @classmethod 678 | def new_from_rgb_hex(cls, hex_str): 679 | """ 680 | Converts an RGB hex string like #RRGGBB and assigns the values to 681 | this sRGBColor object. 682 | 683 | :rtype: sRGBColor 684 | """ 685 | colorstring = hex_str.strip() 686 | if colorstring[0] == "#": 687 | colorstring = colorstring[1:] 688 | if len(colorstring) != 6: 689 | raise ValueError("input #%s is not in #RRGGBB format" % colorstring) 690 | r, g, b = colorstring[:2], colorstring[2:4], colorstring[4:] 691 | r, g, b = [int(n, 16) / 255.0 for n in (r, g, b)] 692 | return cls(r, g, b) 693 | 694 | 695 | # noinspection PyPep8Naming 696 | class sRGBColor(BaseRGBColor): 697 | """ 698 | Represents an sRGB color. 699 | 700 | .. note:: If you pass in upscaled values, we automatically scale them 701 | down to 0.0-1.0. If you need the old upscaled values, you can 702 | retrieve them with :py:meth:`get_upscaled_value_tuple`. 703 | 704 | :ivar float rgb_r: R coordinate 705 | :ivar float rgb_g: G coordinate 706 | :ivar float rgb_b: B coordinate 707 | :ivar bool is_upscaled: If True, RGB values are between 0-255. If False, 708 | 0.0-1.0. 709 | """ 710 | 711 | #: RGB space's gamma constant. 712 | rgb_gamma = 2.2 713 | #: The RGB space's native illuminant. Important when converting to XYZ. 714 | native_illuminant = "d65" 715 | conversion_matrices = { 716 | "xyz_to_rgb": numpy.array( 717 | ( 718 | (3.24071, -1.53726, -0.498571), 719 | (-0.969258, 1.87599, 0.0415557), 720 | (0.0556352, -0.203996, 1.05707), 721 | ) 722 | ), 723 | "rgb_to_xyz": numpy.array( 724 | ( 725 | (0.412424, 0.357579, 0.180464), 726 | (0.212656, 0.715158, 0.0721856), 727 | (0.0193324, 0.119193, 0.950444), 728 | ) 729 | ), 730 | } 731 | 732 | 733 | class BT2020Color(BaseRGBColor): 734 | """ 735 | Represents a ITU-R BT.2020 color. 736 | 737 | .. note:: If you pass in upscaled values, we automatically scale them 738 | down to 0.0-1.0. If you need the old upscaled values, you can 739 | retrieve them with :py:meth:`get_upscaled_value_tuple`. 740 | 741 | :ivar float rgb_r: R coordinate 742 | :ivar float rgb_g: G coordinate 743 | :ivar float rgb_b: B coordinate 744 | :ivar bool is_upscaled: If True, RGB values are between 0-255. If False, 745 | 0.0-1.0. 746 | """ 747 | 748 | #: RGB space's gamma constant. 749 | rgb_gamma = 2.4 750 | #: The RGB space's native illuminant. Important when converting to XYZ. 751 | native_illuminant = "d65" 752 | conversion_matrices = { 753 | "xyz_to_rgb": numpy.array( 754 | ( 755 | (1.716651187971269, -0.355670783776393, -0.253366281373660), 756 | (-0.666684351832489, 1.616481236634939, 0.015768545813911), 757 | (0.017639857445311, -0.042770613257809, 0.942103121235474), 758 | ) 759 | ), 760 | "rgb_to_xyz": numpy.array( 761 | ( 762 | (0.636958048301291, 0.144616903586208, 0.168880975164172), 763 | (0.262700212011267, 0.677998071518871, 0.059301716469862), 764 | (0.000000000000000, 0.028072693049087, 1.060985057710791), 765 | ) 766 | ), 767 | } 768 | 769 | 770 | class AdobeRGBColor(BaseRGBColor): 771 | """ 772 | Represents an Adobe RGB color. 773 | 774 | .. note:: If you pass in upscaled values, we automatically scale them 775 | down to 0.0-1.0. If you need the old upscaled values, you can 776 | retrieve them with :py:meth:`get_upscaled_value_tuple`. 777 | 778 | :ivar float rgb_r: R coordinate 779 | :ivar float rgb_g: G coordinate 780 | :ivar float rgb_b: B coordinate 781 | :ivar bool is_upscaled: If True, RGB values are between 0-255. If False, 782 | 0.0-1.0. 783 | """ 784 | 785 | #: RGB space's gamma constant. 786 | rgb_gamma = 2.2 787 | #: The RGB space's native illuminant. Important when converting to XYZ. 788 | native_illuminant = "d65" 789 | conversion_matrices = { 790 | "xyz_to_rgb": numpy.array( 791 | ( 792 | (2.04148, -0.564977, -0.344713), 793 | (-0.969258, 1.87599, 0.0415557), 794 | (0.0134455, -0.118373, 1.01527), 795 | ) 796 | ), 797 | "rgb_to_xyz": numpy.array( 798 | ( 799 | (0.576700, 0.185556, 0.188212), 800 | (0.297361, 0.627355, 0.0752847), 801 | (0.0270328, 0.0706879, 0.991248), 802 | ) 803 | ), 804 | } 805 | 806 | 807 | class AppleRGBColor(BaseRGBColor): 808 | """ 809 | Represents an AppleRGB color. 810 | 811 | .. note:: If you pass in upscaled values, we automatically scale them 812 | down to 0.0-1.0. If you need the old upscaled values, you can 813 | retrieve them with :py:meth:`get_upscaled_value_tuple`. 814 | 815 | :ivar float rgb_r: R coordinate 816 | :ivar float rgb_g: G coordinate 817 | :ivar float rgb_b: B coordinate 818 | :ivar bool is_upscaled: If True, RGB values are between 0-255. If False, 819 | 0.0-1.0. 820 | """ 821 | 822 | #: RGB space's gamma constant. 823 | rgb_gamma = 1.8 824 | #: The RGB space's native illuminant. Important when converting to XYZ. 825 | native_illuminant = "d65" 826 | conversion_matrices = { 827 | "xyz_to_rgb": numpy.array( 828 | ( 829 | (2.9515373, -1.2894116, -0.4738445), 830 | (-1.0851093, 1.9908566, 0.0372026), 831 | (0.0854934, -0.2694964, 1.0912975), 832 | ) 833 | ), 834 | "rgb_to_xyz": numpy.array( 835 | ( 836 | (0.4497288, 0.3162486, 0.1844926), 837 | (0.2446525, 0.6720283, 0.0833192), 838 | (0.0251848, 0.1411824, 0.9224628), 839 | ) 840 | ), 841 | } 842 | 843 | 844 | class HSLColor(ColorBase): 845 | """ 846 | Represents an HSL color. 847 | """ 848 | 849 | VALUES = ["hsl_h", "hsl_s", "hsl_l"] 850 | 851 | def __init__(self, hsl_h, hsl_s, hsl_l): 852 | """ 853 | :param float hsl_h: H coordinate. 854 | :param float hsl_s: S coordinate. 855 | :param float hsl_l: L coordinate. 856 | """ 857 | super(HSLColor, self).__init__() 858 | #: H coordinate 859 | self.hsl_h = float(hsl_h) 860 | #: S coordinate 861 | self.hsl_s = float(hsl_s) 862 | #: L coordinate 863 | self.hsl_l = float(hsl_l) 864 | 865 | 866 | class HSVColor(ColorBase): 867 | """ 868 | Represents an HSV color. 869 | """ 870 | 871 | VALUES = ["hsv_h", "hsv_s", "hsv_v"] 872 | 873 | def __init__(self, hsv_h, hsv_s, hsv_v): 874 | """ 875 | :param float hsv_h: H coordinate. 876 | :param float hsv_s: S coordinate. 877 | :param float hsv_v: V coordinate. 878 | """ 879 | super(HSVColor, self).__init__() 880 | #: H coordinate 881 | self.hsv_h = float(hsv_h) 882 | #: S coordinate 883 | self.hsv_s = float(hsv_s) 884 | #: V coordinate 885 | self.hsv_v = float(hsv_v) 886 | 887 | 888 | class CMYColor(ColorBase): 889 | """ 890 | Represents a CMY color. 891 | """ 892 | 893 | VALUES = ["cmy_c", "cmy_m", "cmy_y"] 894 | 895 | def __init__(self, cmy_c, cmy_m, cmy_y): 896 | """ 897 | :param float cmy_c: C coordinate. 898 | :param float cmy_m: M coordinate. 899 | :param float cmy_y: Y coordinate. 900 | """ 901 | super(CMYColor, self).__init__() 902 | #: C coordinate 903 | self.cmy_c = float(cmy_c) 904 | #: M coordinate 905 | self.cmy_m = float(cmy_m) 906 | #: Y coordinate 907 | self.cmy_y = float(cmy_y) 908 | 909 | 910 | class CMYKColor(ColorBase): 911 | """ 912 | Represents a CMYK color. 913 | """ 914 | 915 | VALUES = ["cmyk_c", "cmyk_m", "cmyk_y", "cmyk_k"] 916 | 917 | def __init__(self, cmyk_c, cmyk_m, cmyk_y, cmyk_k): 918 | """ 919 | :param float cmyk_c: C coordinate. 920 | :param float cmyk_m: M coordinate. 921 | :param float cmyk_y: Y coordinate. 922 | :param float cmyk_k: K coordinate. 923 | """ 924 | super(CMYKColor, self).__init__() 925 | #: C coordinate 926 | self.cmyk_c = float(cmyk_c) 927 | #: M coordinate 928 | self.cmyk_m = float(cmyk_m) 929 | #: Y coordinate 930 | self.cmyk_y = float(cmyk_y) 931 | #: K coordinate 932 | self.cmyk_k = float(cmyk_k) 933 | 934 | 935 | class IPTColor(ColorBase): 936 | """ 937 | Represents an IPT color. 938 | 939 | Reference: 940 | Fairchild, M. D. (2013). Color appearance models, 3rd Ed. (pp. 271-272). 941 | John Wiley & Sons. 942 | """ 943 | 944 | VALUES = ["ipt_i", "ipt_p", "ipt_t"] 945 | 946 | conversion_matrices = { 947 | "xyz_to_lms": numpy.array( 948 | ( 949 | (0.4002, 0.7075, -0.0807), 950 | (-0.2280, 1.1500, 0.0612), 951 | (0.0000, 0.0000, 0.9184), 952 | ) 953 | ), 954 | "lms_to_ipt": numpy.array( 955 | ( 956 | (0.4000, 0.4000, 0.2000), 957 | (4.4550, -4.8510, 0.3960), 958 | (0.8056, 0.3572, -1.1628), 959 | ) 960 | ), 961 | } 962 | 963 | def __init__(self, ipt_i, ipt_p, ipt_t): 964 | """ 965 | :param ipt_i: I coordinate. 966 | :param ipt_p: P coordinate. 967 | :param ipt_t: T coordinate. 968 | """ 969 | super(IPTColor, self).__init__() 970 | #: I coordinate 971 | self.ipt_i = ipt_i 972 | #: P coordinate 973 | self.ipt_p = ipt_p 974 | #: T coordinate 975 | self.ipt_t = ipt_t 976 | 977 | @property 978 | def hue_angle(self): 979 | return numpy.arctan2(self.ipt_t, self.ipt_p) 980 | -------------------------------------------------------------------------------- /colormath/density.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Formulas for density calculation. 4 | """ 5 | 6 | from math import log10 7 | from colormath.density_standards import ( 8 | ANSI_STATUS_T_BLUE, 9 | ANSI_STATUS_T_GREEN, 10 | ANSI_STATUS_T_RED, 11 | VISUAL_DENSITY_THRESH, 12 | ISO_VISUAL, 13 | ) 14 | 15 | 16 | def ansi_density(color, density_standard): 17 | """ 18 | Calculates density for the given SpectralColor using the spectral weighting 19 | function provided. For example, ANSI_STATUS_T_RED. These may be found in 20 | :py:mod:`colormath.density_standards`. 21 | 22 | :param SpectralColor color: The SpectralColor object to calculate 23 | density for. 24 | :param numpy.ndarray density_standard: NumPy array of filter of choice 25 | from :py:mod:`colormath.density_standards`. 26 | :rtype: float 27 | :returns: The density value for the given color and density standard. 28 | """ 29 | # Load the spec_XXXnm attributes into a Numpy array. 30 | sample = color.get_numpy_array() 31 | # Matrix multiplication 32 | intermediate = sample * density_standard 33 | 34 | # Sum the products. 35 | numerator = intermediate.sum() 36 | # This is the denominator in the density equation. 37 | sum_of_standard_wavelengths = density_standard.sum() 38 | 39 | # This is the top level of the density formula. 40 | return -1.0 * log10(numerator / sum_of_standard_wavelengths) 41 | 42 | 43 | def auto_density(color): 44 | """ 45 | Given a SpectralColor, automatically choose the correct ANSI T filter. 46 | Returns a tuple with a string representation of the filter the 47 | calculated density. 48 | 49 | :param SpectralColor color: The SpectralColor object to calculate 50 | density for. 51 | :rtype: float 52 | :returns: The density value, with the filter selected automatically. 53 | """ 54 | blue_density = ansi_density(color, ANSI_STATUS_T_BLUE) 55 | green_density = ansi_density(color, ANSI_STATUS_T_GREEN) 56 | red_density = ansi_density(color, ANSI_STATUS_T_RED) 57 | 58 | densities = [blue_density, green_density, red_density] 59 | min_density = min(densities) 60 | max_density = max(densities) 61 | density_range = max_density - min_density 62 | 63 | # See comments in density_standards.py for VISUAL_DENSITY_THRESH to 64 | # understand what this is doing. 65 | if density_range <= VISUAL_DENSITY_THRESH: 66 | return ansi_density(color, ISO_VISUAL) 67 | elif blue_density > green_density and blue_density > red_density: 68 | return blue_density 69 | elif green_density > blue_density and green_density > red_density: 70 | return green_density 71 | else: 72 | return red_density 73 | -------------------------------------------------------------------------------- /colormath/density_standards.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Various density standards. 4 | """ 5 | 6 | from numpy import array 7 | 8 | # Visual density is typically used on grey patches. Take a reading and get 9 | # the density values of the Red, Green, and Blue filters. If the difference 10 | # between the highest and lowest value is less than or equal to the value 11 | # below, return the density reading calculated against the ISO Visual spectral 12 | # weighting curve. The X-Rite 500 uses a thresh of 0.05, the X-Rite i1 appears 13 | # to use 0.08. 14 | VISUAL_DENSITY_THRESH = 0.08 15 | 16 | ANSI_STATUS_A_RED = array( 17 | ( 18 | 0.00, 19 | 0.00, 20 | 0.00, 21 | 0.00, 22 | 0.00, 23 | 0.00, 24 | 0.00, 25 | 0.00, 26 | 0.00, 27 | 0.00, 28 | 0.00, 29 | 0.00, 30 | 0.00, 31 | 0.00, 32 | 0.00, 33 | 0.00, 34 | 0.00, 35 | 0.00, 36 | 0.00, 37 | 0.00, 38 | 0.00, 39 | 0.00, 40 | 0.00, 41 | 0.00, 42 | 0.00, 43 | 0.00, 44 | 0.37, 45 | 43.45, 46 | 100.00, 47 | 74.30, 48 | 40.18, 49 | 19.32, 50 | 7.94, 51 | 3.56, 52 | 1.46, 53 | 0.60, 54 | 0.24, 55 | 0.09, 56 | 0.04, 57 | 0.01, 58 | 0.01, 59 | 0.00, 60 | 0.00, 61 | 0.00, 62 | 0.00, 63 | 0.00, 64 | 0.00, 65 | 0.00, 66 | 0.00, 67 | 0.00, 68 | ) 69 | ) 70 | 71 | ANSI_STATUS_A_GREEN = array( 72 | ( 73 | 0.00, 74 | 0.00, 75 | 0.00, 76 | 0.00, 77 | 0.00, 78 | 0.00, 79 | 0.00, 80 | 0.00, 81 | 0.00, 82 | 0.00, 83 | 0.00, 84 | 0.00, 85 | 0.00, 86 | 0.00, 87 | 0.00, 88 | 0.00, 89 | 0.04, 90 | 6.64, 91 | 60.53, 92 | 100.00, 93 | 80.54, 94 | 44.06, 95 | 16.63, 96 | 4.06, 97 | 0.58, 98 | 0.04, 99 | 0.00, 100 | 0.00, 101 | 0.00, 102 | 0.00, 103 | 0.00, 104 | 0.00, 105 | 0.00, 106 | 0.00, 107 | 0.00, 108 | 0.00, 109 | 0.00, 110 | 0.00, 111 | 0.00, 112 | 0.00, 113 | 0.00, 114 | 0.00, 115 | 0.00, 116 | 0.00, 117 | 0.00, 118 | 0.00, 119 | 0.00, 120 | 0.00, 121 | 0.00, 122 | 0.00, 123 | ) 124 | ) 125 | 126 | ANSI_STATUS_A_BLUE = array( 127 | ( 128 | 0.00, 129 | 0.00, 130 | 0.00, 131 | 0.00, 132 | 0.00, 133 | 0.00, 134 | 0.00, 135 | 0.00, 136 | 4.00, 137 | 65.92, 138 | 100.00, 139 | 81.66, 140 | 41.69, 141 | 10.96, 142 | 0.79, 143 | 0.04, 144 | 0.00, 145 | 0.00, 146 | 0.00, 147 | 0.00, 148 | 0.00, 149 | 0.00, 150 | 0.00, 151 | 0.00, 152 | 0.00, 153 | 0.00, 154 | 0.00, 155 | 0.00, 156 | 0.00, 157 | 0.00, 158 | 0.00, 159 | 0.00, 160 | 0.00, 161 | 0.00, 162 | 0.00, 163 | 0.00, 164 | 0.00, 165 | 0.00, 166 | 0.00, 167 | 0.00, 168 | 0.00, 169 | 0.00, 170 | 0.00, 171 | 0.00, 172 | 0.00, 173 | 0.00, 174 | 0.00, 175 | 0.00, 176 | 0.00, 177 | 0.00, 178 | ) 179 | ) 180 | 181 | ANSI_STATUS_E_RED = array( 182 | ( 183 | 0.00, 184 | 0.00, 185 | 0.00, 186 | 0.00, 187 | 0.00, 188 | 0.00, 189 | 0.00, 190 | 0.00, 191 | 0.00, 192 | 0.00, 193 | 0.00, 194 | 0.00, 195 | 0.00, 196 | 0.00, 197 | 0.00, 198 | 0.00, 199 | 0.00, 200 | 0.00, 201 | 0.00, 202 | 0.00, 203 | 0.00, 204 | 0.00, 205 | 0.01, 206 | 0.06, 207 | 0.45, 208 | 29.99, 209 | 100.00, 210 | 84.92, 211 | 54.95, 212 | 25.00, 213 | 10.00, 214 | 5.00, 215 | 1.50, 216 | 0.50, 217 | 0.30, 218 | 0.15, 219 | 0.05, 220 | 0.01, 221 | 0.00, 222 | 0.00, 223 | 0.00, 224 | 0.00, 225 | 0.00, 226 | 0.00, 227 | 0.00, 228 | 0.00, 229 | 0.00, 230 | 0.00, 231 | 0.00, 232 | 0.00, 233 | ) 234 | ) 235 | 236 | ANSI_STATUS_E_GREEN = array( 237 | ( 238 | 0.00, 239 | 0.00, 240 | 0.00, 241 | 0.00, 242 | 0.00, 243 | 0.00, 244 | 0.00, 245 | 0.00, 246 | 0.00, 247 | 0.00, 248 | 0.00, 249 | 0.00, 250 | 0.00, 251 | 0.01, 252 | 1.00, 253 | 5.00, 254 | 27.99, 255 | 68.08, 256 | 92.04, 257 | 100.00, 258 | 87.90, 259 | 66.07, 260 | 41.98, 261 | 21.98, 262 | 8.99, 263 | 2.50, 264 | 0.70, 265 | 0.09, 266 | 0.01, 267 | 0.00, 268 | 0.00, 269 | 0.00, 270 | 0.00, 271 | 0.00, 272 | 0.00, 273 | 0.00, 274 | 0.00, 275 | 0.00, 276 | 0.00, 277 | 0.00, 278 | 0.00, 279 | 0.00, 280 | 0.00, 281 | 0.00, 282 | 0.00, 283 | 0.00, 284 | 0.00, 285 | 0.00, 286 | 0.00, 287 | 0.00, 288 | ) 289 | ) 290 | 291 | ANSI_STATUS_E_BLUE = array( 292 | ( 293 | 0.00, 294 | 0.00, 295 | 0.00, 296 | 0.01, 297 | 0.27, 298 | 2.70, 299 | 13.00, 300 | 29.99, 301 | 59.98, 302 | 82.04, 303 | 100.00, 304 | 90.99, 305 | 76.03, 306 | 46.99, 307 | 17.99, 308 | 6.00, 309 | 0.80, 310 | 0.05, 311 | 0.01, 312 | 0.00, 313 | 0.00, 314 | 0.00, 315 | 0.00, 316 | 0.00, 317 | 0.00, 318 | 0.00, 319 | 0.00, 320 | 0.00, 321 | 0.00, 322 | 0.00, 323 | 0.00, 324 | 0.00, 325 | 0.00, 326 | 0.00, 327 | 0.00, 328 | 0.00, 329 | 0.00, 330 | 0.00, 331 | 0.00, 332 | 0.00, 333 | 0.00, 334 | 0.00, 335 | 0.00, 336 | 0.00, 337 | 0.00, 338 | 0.00, 339 | 0.00, 340 | 0.00, 341 | 0.00, 342 | 0.00, 343 | ) 344 | ) 345 | 346 | ANSI_STATUS_M_RED = array( 347 | ( 348 | 0.00, 349 | 0.00, 350 | 0.00, 351 | 0.00, 352 | 0.00, 353 | 0.00, 354 | 0.00, 355 | 0.00, 356 | 0.00, 357 | 0.00, 358 | 0.00, 359 | 0.00, 360 | 0.00, 361 | 0.00, 362 | 0.00, 363 | 0.00, 364 | 0.00, 365 | 0.00, 366 | 0.00, 367 | 0.00, 368 | 0.00, 369 | 0.00, 370 | 0.00, 371 | 0.00, 372 | 0.00, 373 | 0.00, 374 | 0.00, 375 | 0.00, 376 | 0.13, 377 | 30.13, 378 | 100.00, 379 | 79.25, 380 | 37.84, 381 | 17.86, 382 | 7.50, 383 | 3.10, 384 | 1.26, 385 | 0.49, 386 | 0.19, 387 | 0.07, 388 | 0.03, 389 | 0.01, 390 | 0.00, 391 | 0.00, 392 | 0.00, 393 | 0.00, 394 | 0.00, 395 | 0.00, 396 | 0.00, 397 | 0.00, 398 | ) 399 | ) 400 | 401 | ANSI_STATUS_M_GREEN = array( 402 | ( 403 | 0.00, 404 | 0.00, 405 | 0.00, 406 | 0.00, 407 | 0.00, 408 | 0.00, 409 | 0.00, 410 | 0.00, 411 | 0.00, 412 | 0.00, 413 | 0.00, 414 | 0.00, 415 | 0.00, 416 | 0.01, 417 | 0.16, 418 | 1.43, 419 | 6.37, 420 | 18.71, 421 | 42.27, 422 | 74.47, 423 | 100.00, 424 | 98.86, 425 | 65.77, 426 | 28.71, 427 | 8.22, 428 | 1.49, 429 | 0.17, 430 | 0.01, 431 | 0.00, 432 | 0.00, 433 | 0.00, 434 | 0.00, 435 | 0.00, 436 | 0.00, 437 | 0.00, 438 | 0.00, 439 | 0.00, 440 | 0.00, 441 | 0.00, 442 | 0.00, 443 | 0.00, 444 | 0.00, 445 | 0.00, 446 | 0.00, 447 | 0.00, 448 | 0.00, 449 | 0.00, 450 | 0.00, 451 | 0.00, 452 | 0.00, 453 | ) 454 | ) 455 | 456 | ANSI_STATUS_M_BLUE = array( 457 | ( 458 | 0.00, 459 | 0.00, 460 | 0.00, 461 | 0.00, 462 | 0.00, 463 | 0.00, 464 | 0.00, 465 | 0.13, 466 | 12.91, 467 | 42.85, 468 | 74.30, 469 | 100.00, 470 | 90.16, 471 | 55.34, 472 | 22.03, 473 | 5.53, 474 | 0.98, 475 | 0.07, 476 | 0.00, 477 | 0.00, 478 | 0.00, 479 | 0.00, 480 | 0.00, 481 | 0.00, 482 | 0.00, 483 | 0.00, 484 | 0.00, 485 | 0.00, 486 | 0.00, 487 | 0.00, 488 | 0.00, 489 | 0.00, 490 | 0.00, 491 | 0.00, 492 | 0.00, 493 | 0.00, 494 | 0.00, 495 | 0.00, 496 | 0.00, 497 | 0.00, 498 | 0.00, 499 | 0.00, 500 | 0.00, 501 | 0.00, 502 | 0.00, 503 | 0.00, 504 | 0.00, 505 | 0.00, 506 | 0.00, 507 | 0.00, 508 | ) 509 | ) 510 | 511 | ANSI_STATUS_T_RED = array( 512 | ( 513 | 0.00, 514 | 0.00, 515 | 0.00, 516 | 0.00, 517 | 0.00, 518 | 0.00, 519 | 0.00, 520 | 0.00, 521 | 0.00, 522 | 0.00, 523 | 0.00, 524 | 0.00, 525 | 0.00, 526 | 0.00, 527 | 0.00, 528 | 0.00, 529 | 0.00, 530 | 0.00, 531 | 0.00, 532 | 0.00, 533 | 0.00, 534 | 0.00, 535 | 0.00, 536 | 0.06, 537 | 0.45, 538 | 29.99, 539 | 100.00, 540 | 84.92, 541 | 54.95, 542 | 25.00, 543 | 10.00, 544 | 5.00, 545 | 1.50, 546 | 0.50, 547 | 0.30, 548 | 0.15, 549 | 0.05, 550 | 0.01, 551 | 0.00, 552 | 0.00, 553 | 0.00, 554 | 0.00, 555 | 0.00, 556 | 0.00, 557 | 0.00, 558 | 0.00, 559 | 0.00, 560 | 0.00, 561 | 0.00, 562 | 0.00, 563 | ) 564 | ) 565 | 566 | ANSI_STATUS_T_GREEN = array( 567 | ( 568 | 0.00, 569 | 0.00, 570 | 0.00, 571 | 0.00, 572 | 0.00, 573 | 0.00, 574 | 0.00, 575 | 0.00, 576 | 0.00, 577 | 0.00, 578 | 0.00, 579 | 0.00, 580 | 0.00, 581 | 0.00, 582 | 1.00, 583 | 5.00, 584 | 27.99, 585 | 68.08, 586 | 92.04, 587 | 100.00, 588 | 87.90, 589 | 66.07, 590 | 41.98, 591 | 21.98, 592 | 8.99, 593 | 2.50, 594 | 0.70, 595 | 0.09, 596 | 0.01, 597 | 0.00, 598 | 0.00, 599 | 0.00, 600 | 0.00, 601 | 0.00, 602 | 0.00, 603 | 0.00, 604 | 0.00, 605 | 0.00, 606 | 0.00, 607 | 0.00, 608 | 0.00, 609 | 0.00, 610 | 0.00, 611 | 0.00, 612 | 0.00, 613 | 0.00, 614 | 0.00, 615 | 0.00, 616 | 0.00, 617 | 0.00, 618 | ) 619 | ) 620 | 621 | ANSI_STATUS_T_BLUE = array( 622 | ( 623 | 0.00, 624 | 0.01, 625 | 0.02, 626 | 0.10, 627 | 0.30, 628 | 1.50, 629 | 6.00, 630 | 16.98, 631 | 39.99, 632 | 59.98, 633 | 82.04, 634 | 93.97, 635 | 100.00, 636 | 97.05, 637 | 84.92, 638 | 65.01, 639 | 39.99, 640 | 17.99, 641 | 5.00, 642 | 0.20, 643 | 0.04, 644 | 0.00, 645 | 0.00, 646 | 0.00, 647 | 0.00, 648 | 0.00, 649 | 0.00, 650 | 0.00, 651 | 0.00, 652 | 0.00, 653 | 0.00, 654 | 0.00, 655 | 0.00, 656 | 0.00, 657 | 0.00, 658 | 0.00, 659 | 0.00, 660 | 0.00, 661 | 0.00, 662 | 0.00, 663 | 0.00, 664 | 0.00, 665 | 0.00, 666 | 0.00, 667 | 0.00, 668 | 0.00, 669 | 0.00, 670 | 0.00, 671 | 0.00, 672 | 0.00, 673 | ) 674 | ) 675 | 676 | TYPE1 = array( 677 | ( 678 | 0.00, 679 | 0.00, 680 | 0.01, 681 | 0.04, 682 | 0.72, 683 | 28.84, 684 | 100.00, 685 | 28.84, 686 | 0.72, 687 | 0.04, 688 | 0.01, 689 | 0.00, 690 | 0.00, 691 | 0.00, 692 | 0.00, 693 | 0.00, 694 | 0.00, 695 | 0.00, 696 | 0.00, 697 | 0.00, 698 | 0.00, 699 | 0.00, 700 | 0.00, 701 | 0.00, 702 | 0.00, 703 | 0.00, 704 | 0.00, 705 | 0.00, 706 | 0.00, 707 | 0.00, 708 | 0.00, 709 | 0.00, 710 | 0.00, 711 | 0.00, 712 | 0.00, 713 | 0.00, 714 | 0.00, 715 | 0.00, 716 | 0.00, 717 | 0.00, 718 | 0.00, 719 | 0.00, 720 | 0.00, 721 | 0.00, 722 | 0.00, 723 | 0.00, 724 | 0.00, 725 | 0.00, 726 | 0.00, 727 | 0.00, 728 | ) 729 | ) 730 | 731 | TYPE2 = array( 732 | ( 733 | 0.01, 734 | 0.51, 735 | 19.05, 736 | 38.28, 737 | 57.54, 738 | 70.96, 739 | 82.41, 740 | 90.36, 741 | 97.27, 742 | 100.00, 743 | 97.72, 744 | 89.33, 745 | 73.11, 746 | 55.34, 747 | 38.19, 748 | 22.44, 749 | 9.84, 750 | 2.52, 751 | 0.64, 752 | 0.16, 753 | 0.01, 754 | 0.00, 755 | 0.00, 756 | 0.00, 757 | 0.00, 758 | 0.00, 759 | 0.00, 760 | 0.00, 761 | 0.00, 762 | 0.00, 763 | 0.00, 764 | 0.00, 765 | 0.00, 766 | 0.00, 767 | 0.00, 768 | 0.00, 769 | 0.00, 770 | 0.00, 771 | 0.00, 772 | 0.00, 773 | 0.00, 774 | 0.00, 775 | 0.00, 776 | 0.00, 777 | 0.00, 778 | 0.00, 779 | 0.00, 780 | 0.00, 781 | 0.00, 782 | 0.00, 783 | ) 784 | ) 785 | 786 | ISO_VISUAL = array( 787 | ( 788 | 0.00, 789 | 0.00, 790 | 0.00, 791 | 0.00, 792 | 0.00, 793 | 0.00, 794 | 0.01, 795 | 0.02, 796 | 0.08, 797 | 0.28, 798 | 0.65, 799 | 1.23, 800 | 2.22, 801 | 3.82, 802 | 6.58, 803 | 10.99, 804 | 18.88, 805 | 32.58, 806 | 50.35, 807 | 66.83, 808 | 80.35, 809 | 90.57, 810 | 97.50, 811 | 100.00, 812 | 97.50, 813 | 90.36, 814 | 79.80, 815 | 67.14, 816 | 53.83, 817 | 39.17, 818 | 27.10, 819 | 17.30, 820 | 10.30, 821 | 5.61, 822 | 3.09, 823 | 1.54, 824 | 0.80, 825 | 0.42, 826 | 0.22, 827 | 0.11, 828 | 0.05, 829 | 0.03, 830 | 0.01, 831 | 0.01, 832 | 0.00, 833 | 0.00, 834 | 0.00, 835 | 0.00, 836 | 0.00, 837 | 0.00, 838 | ) 839 | ) 840 | -------------------------------------------------------------------------------- /colormath/spectral_constants.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Contains lookup tables, constants, and things that are generally static 4 | and useful throughout the library. 5 | """ 6 | import numpy 7 | 8 | STDOBSERV_X2 = numpy.array( 9 | ( 10 | 0.000000000000, 11 | 0.000000000000, 12 | 0.000129900000, 13 | 0.000414900000, 14 | 0.001368000000, 15 | 0.004243000000, 16 | 0.014310000000, 17 | 0.043510000000, 18 | 0.134380000000, 19 | 0.283900000000, 20 | 0.348280000000, 21 | 0.336200000000, 22 | 0.290800000000, 23 | 0.195360000000, 24 | 0.095640000000, 25 | 0.032010000000, 26 | 0.004900000000, 27 | 0.009300000000, 28 | 0.063270000000, 29 | 0.165500000000, 30 | 0.290400000000, 31 | 0.433449900000, 32 | 0.594500000000, 33 | 0.762100000000, 34 | 0.916300000000, 35 | 1.026300000000, 36 | 1.062200000000, 37 | 1.002600000000, 38 | 0.854449900000, 39 | 0.642400000000, 40 | 0.447900000000, 41 | 0.283500000000, 42 | 0.164900000000, 43 | 0.087400000000, 44 | 0.046770000000, 45 | 0.022700000000, 46 | 0.011359160000, 47 | 0.005790346000, 48 | 0.002899327000, 49 | 0.001439971000, 50 | 0.000690078600, 51 | 0.000332301100, 52 | 0.000166150500, 53 | 0.000083075270, 54 | 0.000041509940, 55 | 0.000020673830, 56 | 0.000010253980, 57 | 0.000005085868, 58 | 0.000002522525, 59 | 0.000001251141, 60 | ) 61 | ) 62 | 63 | STDOBSERV_Y2 = numpy.array( 64 | ( 65 | 0.000000000000, 66 | 0.000000000000, 67 | 0.000003917000, 68 | 0.000012390000, 69 | 0.000039000000, 70 | 0.000120000000, 71 | 0.000396000000, 72 | 0.001210000000, 73 | 0.004000000000, 74 | 0.011600000000, 75 | 0.023000000000, 76 | 0.038000000000, 77 | 0.060000000000, 78 | 0.090980000000, 79 | 0.139020000000, 80 | 0.208020000000, 81 | 0.323000000000, 82 | 0.503000000000, 83 | 0.710000000000, 84 | 0.862000000000, 85 | 0.954000000000, 86 | 0.994950100000, 87 | 0.995000000000, 88 | 0.952000000000, 89 | 0.870000000000, 90 | 0.757000000000, 91 | 0.631000000000, 92 | 0.503000000000, 93 | 0.381000000000, 94 | 0.265000000000, 95 | 0.175000000000, 96 | 0.107000000000, 97 | 0.061000000000, 98 | 0.032000000000, 99 | 0.017000000000, 100 | 0.008210000000, 101 | 0.004102000000, 102 | 0.002091000000, 103 | 0.001047000000, 104 | 0.000520000000, 105 | 0.000249200000, 106 | 0.000120000000, 107 | 0.000060000000, 108 | 0.000030000000, 109 | 0.000014990000, 110 | 0.000007465700, 111 | 0.000003702900, 112 | 0.000001836600, 113 | 0.000000910930, 114 | 0.000000451810, 115 | ) 116 | ) 117 | 118 | STDOBSERV_Z2 = numpy.array( 119 | ( 120 | 0.000000000000, 121 | 0.000000000000, 122 | 0.000606100000, 123 | 0.001946000000, 124 | 0.006450001000, 125 | 0.020050010000, 126 | 0.067850010000, 127 | 0.207400000000, 128 | 0.645600000000, 129 | 1.385600000000, 130 | 1.747060000000, 131 | 1.772110000000, 132 | 1.669200000000, 133 | 1.287640000000, 134 | 0.812950100000, 135 | 0.465180000000, 136 | 0.272000000000, 137 | 0.158200000000, 138 | 0.078249990000, 139 | 0.042160000000, 140 | 0.020300000000, 141 | 0.008749999000, 142 | 0.003900000000, 143 | 0.002100000000, 144 | 0.001650001000, 145 | 0.001100000000, 146 | 0.000800000000, 147 | 0.000340000000, 148 | 0.000190000000, 149 | 0.000049999990, 150 | 0.000020000000, 151 | 0.000000000000, 152 | 0.000000000000, 153 | 0.000000000000, 154 | 0.000000000000, 155 | 0.000000000000, 156 | 0.000000000000, 157 | 0.000000000000, 158 | 0.000000000000, 159 | 0.000000000000, 160 | 0.000000000000, 161 | 0.000000000000, 162 | 0.000000000000, 163 | 0.000000000000, 164 | 0.000000000000, 165 | 0.000000000000, 166 | 0.000000000000, 167 | 0.000000000000, 168 | 0.000000000000, 169 | 0.000000000000, 170 | ) 171 | ) 172 | 173 | STDOBSERV_X10 = numpy.array( 174 | ( 175 | 0.000000000000, 176 | 0.000000000000, 177 | 0.000000122200, 178 | 0.000005958600, 179 | 0.000159952000, 180 | 0.002361600000, 181 | 0.019109700000, 182 | 0.084736000000, 183 | 0.204492000000, 184 | 0.314679000000, 185 | 0.383734000000, 186 | 0.370702000000, 187 | 0.302273000000, 188 | 0.195618000000, 189 | 0.080507000000, 190 | 0.016172000000, 191 | 0.003816000000, 192 | 0.037465000000, 193 | 0.117749000000, 194 | 0.236491000000, 195 | 0.376772000000, 196 | 0.529826000000, 197 | 0.705224000000, 198 | 0.878655000000, 199 | 1.014160000000, 200 | 1.118520000000, 201 | 1.123990000000, 202 | 1.030480000000, 203 | 0.856297000000, 204 | 0.647467000000, 205 | 0.431567000000, 206 | 0.268329000000, 207 | 0.152568000000, 208 | 0.081260600000, 209 | 0.040850800000, 210 | 0.019941300000, 211 | 0.009576880000, 212 | 0.004552630000, 213 | 0.002174960000, 214 | 0.001044760000, 215 | 0.000508258000, 216 | 0.000250969000, 217 | 0.000126390000, 218 | 0.000064525800, 219 | 0.000033411700, 220 | 0.000017611500, 221 | 0.000009413630, 222 | 0.000005093470, 223 | 0.000002795310, 224 | 0.000001553140, 225 | ) 226 | ) 227 | 228 | STDOBSERV_Y10 = numpy.array( 229 | ( 230 | 0.000000000000, 231 | 0.000000000000, 232 | 0.000000013398, 233 | 0.000000651100, 234 | 0.000017364000, 235 | 0.000253400000, 236 | 0.002004400000, 237 | 0.008756000000, 238 | 0.021391000000, 239 | 0.038676000000, 240 | 0.062077000000, 241 | 0.089456000000, 242 | 0.128201000000, 243 | 0.185190000000, 244 | 0.253589000000, 245 | 0.339133000000, 246 | 0.460777000000, 247 | 0.606741000000, 248 | 0.761757000000, 249 | 0.875211000000, 250 | 0.961988000000, 251 | 0.991761000000, 252 | 0.997340000000, 253 | 0.955552000000, 254 | 0.868934000000, 255 | 0.777405000000, 256 | 0.658341000000, 257 | 0.527963000000, 258 | 0.398057000000, 259 | 0.283493000000, 260 | 0.179828000000, 261 | 0.107633000000, 262 | 0.060281000000, 263 | 0.031800400000, 264 | 0.015905100000, 265 | 0.007748800000, 266 | 0.003717740000, 267 | 0.001768470000, 268 | 0.000846190000, 269 | 0.000407410000, 270 | 0.000198730000, 271 | 0.000098428000, 272 | 0.000049737000, 273 | 0.000025486000, 274 | 0.000013249000, 275 | 0.000007012800, 276 | 0.000003764730, 277 | 0.000002046130, 278 | 0.000001128090, 279 | 0.000000629700, 280 | ) 281 | ) 282 | 283 | STDOBSERV_Z10 = numpy.array( 284 | ( 285 | 0.000000000000, 286 | 0.000000000000, 287 | 0.000000535027, 288 | 0.000026143700, 289 | 0.000704776000, 290 | 0.010482200000, 291 | 0.086010900000, 292 | 0.389366000000, 293 | 0.972542000000, 294 | 1.553480000000, 295 | 1.967280000000, 296 | 1.994800000000, 297 | 1.745370000000, 298 | 1.317560000000, 299 | 0.772125000000, 300 | 0.415254000000, 301 | 0.218502000000, 302 | 0.112044000000, 303 | 0.060709000000, 304 | 0.030451000000, 305 | 0.013676000000, 306 | 0.003988000000, 307 | 0.000000000000, 308 | 0.000000000000, 309 | 0.000000000000, 310 | 0.000000000000, 311 | 0.000000000000, 312 | 0.000000000000, 313 | 0.000000000000, 314 | 0.000000000000, 315 | 0.000000000000, 316 | 0.000000000000, 317 | 0.000000000000, 318 | 0.000000000000, 319 | 0.000000000000, 320 | 0.000000000000, 321 | 0.000000000000, 322 | 0.000000000000, 323 | 0.000000000000, 324 | 0.000000000000, 325 | 0.000000000000, 326 | 0.000000000000, 327 | 0.000000000000, 328 | 0.000000000000, 329 | 0.000000000000, 330 | 0.000000000000, 331 | 0.000000000000, 332 | 0.000000000000, 333 | 0.000000000000, 334 | 0.000000000000, 335 | ) 336 | ) 337 | 338 | REFERENCE_ILLUM_A = numpy.array( 339 | ( 340 | 3.59, 341 | 4.75, 342 | 6.15, 343 | 7.83, 344 | 9.80, 345 | 12.09, 346 | 14.72, 347 | 17.69, 348 | 21.01, 349 | 24.68, 350 | 28.71, 351 | 33.10, 352 | 37.82, 353 | 42.88, 354 | 48.25, 355 | 53.92, 356 | 59.87, 357 | 66.07, 358 | 72.50, 359 | 79.14, 360 | 85.95, 361 | 92.91, 362 | 100.00, 363 | 107.18, 364 | 114.43, 365 | 121.72, 366 | 129.03, 367 | 136.33, 368 | 143.60, 369 | 150.81, 370 | 157.95, 371 | 164.99, 372 | 171.92, 373 | 178.72, 374 | 185.38, 375 | 191.88, 376 | 198.20, 377 | 204.34, 378 | 210.29, 379 | 216.04, 380 | 221.58, 381 | 226.91, 382 | 232.02, 383 | 236.91, 384 | 241.57, 385 | 246.01, 386 | 250.21, 387 | 254.19, 388 | 257.95, 389 | 261.47, 390 | ) 391 | ) 392 | 393 | REFERENCE_ILLUM_B = numpy.array( 394 | ( 395 | 2.40, 396 | 5.60, 397 | 9.60, 398 | 15.20, 399 | 22.40, 400 | 31.30, 401 | 41.30, 402 | 52.10, 403 | 63.20, 404 | 73.10, 405 | 80.80, 406 | 85.40, 407 | 88.30, 408 | 92.00, 409 | 95.20, 410 | 96.50, 411 | 94.20, 412 | 90.70, 413 | 89.50, 414 | 92.20, 415 | 96.90, 416 | 101.00, 417 | 102.80, 418 | 102.60, 419 | 101.00, 420 | 99.20, 421 | 98.00, 422 | 98.50, 423 | 99.70, 424 | 101.00, 425 | 102.20, 426 | 103.90, 427 | 105.00, 428 | 104.90, 429 | 103.90, 430 | 101.60, 431 | 99.10, 432 | 96.20, 433 | 92.90, 434 | 89.40, 435 | 86.90, 436 | 85.20, 437 | 84.70, 438 | 85.40, 439 | 0.00, 440 | 0.00, 441 | 0.00, 442 | 0.00, 443 | 0.00, 444 | 0.00, 445 | ) 446 | ) 447 | 448 | REFERENCE_ILLUM_C = numpy.array( 449 | ( 450 | 2.70, 451 | 7.00, 452 | 12.90, 453 | 21.40, 454 | 33.00, 455 | 47.40, 456 | 63.30, 457 | 80.60, 458 | 98.10, 459 | 112.40, 460 | 121.50, 461 | 124.00, 462 | 123.10, 463 | 123.80, 464 | 123.90, 465 | 120.70, 466 | 112.10, 467 | 102.30, 468 | 96.90, 469 | 98.00, 470 | 102.10, 471 | 105.20, 472 | 105.30, 473 | 102.30, 474 | 97.80, 475 | 93.20, 476 | 89.70, 477 | 88.40, 478 | 88.10, 479 | 88.00, 480 | 87.80, 481 | 88.20, 482 | 87.90, 483 | 86.30, 484 | 84.00, 485 | 80.20, 486 | 76.30, 487 | 72.40, 488 | 68.30, 489 | 64.40, 490 | 61.50, 491 | 59.20, 492 | 58.10, 493 | 58.20, 494 | 0.00, 495 | 0.00, 496 | 0.00, 497 | 0.00, 498 | 0.00, 499 | 0.00, 500 | ) 501 | ) 502 | 503 | REFERENCE_ILLUM_D50 = numpy.array( 504 | ( 505 | 17.92, 506 | 20.98, 507 | 23.91, 508 | 25.89, 509 | 24.45, 510 | 29.83, 511 | 49.25, 512 | 56.45, 513 | 59.97, 514 | 57.76, 515 | 74.77, 516 | 87.19, 517 | 90.56, 518 | 91.32, 519 | 95.07, 520 | 91.93, 521 | 95.70, 522 | 96.59, 523 | 97.11, 524 | 102.09, 525 | 100.75, 526 | 102.31, 527 | 100.00, 528 | 97.74, 529 | 98.92, 530 | 93.51, 531 | 97.71, 532 | 99.29, 533 | 99.07, 534 | 95.75, 535 | 98.90, 536 | 95.71, 537 | 98.24, 538 | 103.06, 539 | 99.19, 540 | 87.43, 541 | 91.66, 542 | 92.94, 543 | 76.89, 544 | 86.56, 545 | 92.63, 546 | 78.27, 547 | 57.72, 548 | 82.97, 549 | 78.31, 550 | 79.59, 551 | 73.44, 552 | 63.95, 553 | 70.81, 554 | 74.48, 555 | ) 556 | ) 557 | 558 | REFERENCE_ILLUM_D65 = numpy.array( 559 | ( 560 | 39.90, 561 | 44.86, 562 | 46.59, 563 | 51.74, 564 | 49.92, 565 | 54.60, 566 | 82.69, 567 | 91.42, 568 | 93.37, 569 | 86.63, 570 | 104.81, 571 | 116.96, 572 | 117.76, 573 | 114.82, 574 | 115.89, 575 | 108.78, 576 | 109.33, 577 | 107.78, 578 | 104.78, 579 | 107.68, 580 | 104.40, 581 | 104.04, 582 | 100.00, 583 | 96.34, 584 | 95.79, 585 | 88.69, 586 | 90.02, 587 | 89.61, 588 | 87.71, 589 | 83.30, 590 | 83.72, 591 | 80.05, 592 | 80.24, 593 | 82.30, 594 | 78.31, 595 | 69.74, 596 | 71.63, 597 | 74.37, 598 | 61.62, 599 | 69.91, 600 | 75.11, 601 | 63.61, 602 | 46.43, 603 | 66.83, 604 | 63.40, 605 | 64.32, 606 | 59.47, 607 | 51.97, 608 | 57.46, 609 | 60.33, 610 | ) 611 | ) 612 | 613 | REFERENCE_ILLUM_E = numpy.array( 614 | ( 615 | 100.00, 616 | 100.00, 617 | 100.00, 618 | 100.00, 619 | 100.00, 620 | 100.00, 621 | 100.00, 622 | 100.00, 623 | 100.00, 624 | 100.00, 625 | 100.00, 626 | 100.00, 627 | 100.00, 628 | 100.00, 629 | 100.00, 630 | 100.00, 631 | 100.00, 632 | 100.00, 633 | 100.00, 634 | 100.00, 635 | 100.00, 636 | 100.00, 637 | 100.00, 638 | 100.00, 639 | 100.00, 640 | 100.00, 641 | 100.00, 642 | 100.00, 643 | 100.00, 644 | 100.00, 645 | 100.00, 646 | 100.00, 647 | 100.00, 648 | 100.00, 649 | 100.00, 650 | 100.00, 651 | 100.00, 652 | 100.00, 653 | 100.00, 654 | 100.00, 655 | 100.00, 656 | 100.00, 657 | 100.00, 658 | 100.00, 659 | 100.00, 660 | 100.00, 661 | 100.00, 662 | 100.00, 663 | 100.00, 664 | 100.00, 665 | ) 666 | ) 667 | 668 | REFERENCE_ILLUM_F2 = numpy.array( 669 | ( 670 | 0.00, 671 | 0.00, 672 | 0.00, 673 | 0.00, 674 | 1.18, 675 | 1.84, 676 | 3.44, 677 | 3.85, 678 | 4.19, 679 | 5.06, 680 | 11.81, 681 | 6.63, 682 | 7.19, 683 | 7.54, 684 | 7.65, 685 | 7.62, 686 | 7.28, 687 | 7.05, 688 | 7.16, 689 | 8.04, 690 | 10.01, 691 | 16.64, 692 | 16.16, 693 | 18.62, 694 | 22.79, 695 | 18.66, 696 | 16.54, 697 | 13.80, 698 | 10.95, 699 | 8.40, 700 | 6.31, 701 | 4.68, 702 | 3.45, 703 | 2.55, 704 | 1.89, 705 | 1.53, 706 | 1.10, 707 | 0.88, 708 | 0.68, 709 | 0.56, 710 | 0.51, 711 | 0.47, 712 | 0.46, 713 | 0.40, 714 | 0.27, 715 | 0.00, 716 | 0.00, 717 | 0.00, 718 | 0.00, 719 | 0.00, 720 | ) 721 | ) 722 | 723 | REFERENCE_ILLUM_F7 = numpy.array( 724 | ( 725 | 0.00, 726 | 0.00, 727 | 0.00, 728 | 0.00, 729 | 2.56, 730 | 3.84, 731 | 6.15, 732 | 7.37, 733 | 7.71, 734 | 9.15, 735 | 17.52, 736 | 12.00, 737 | 13.08, 738 | 13.71, 739 | 13.95, 740 | 13.82, 741 | 13.43, 742 | 13.08, 743 | 12.78, 744 | 12.44, 745 | 12.26, 746 | 17.05, 747 | 12.58, 748 | 12.83, 749 | 16.75, 750 | 12.67, 751 | 12.19, 752 | 11.60, 753 | 11.12, 754 | 10.76, 755 | 10.11, 756 | 10.02, 757 | 9.87, 758 | 7.27, 759 | 5.83, 760 | 5.04, 761 | 4.12, 762 | 3.46, 763 | 2.73, 764 | 2.25, 765 | 1.90, 766 | 1.62, 767 | 1.45, 768 | 1.17, 769 | 0.81, 770 | 0.00, 771 | 0.00, 772 | 0.00, 773 | 0.00, 774 | 0.00, 775 | ) 776 | ) 777 | 778 | REFERENCE_ILLUM_F11 = numpy.array( 779 | ( 780 | 0.00, 781 | 0.00, 782 | 0.00, 783 | 0.00, 784 | 0.91, 785 | 0.46, 786 | 1.29, 787 | 1.59, 788 | 2.46, 789 | 4.49, 790 | 12.13, 791 | 7.19, 792 | 6.72, 793 | 5.46, 794 | 5.66, 795 | 14.96, 796 | 4.72, 797 | 1.47, 798 | 0.89, 799 | 1.18, 800 | 39.59, 801 | 32.61, 802 | 2.83, 803 | 1.67, 804 | 11.28, 805 | 12.73, 806 | 7.33, 807 | 55.27, 808 | 13.18, 809 | 12.26, 810 | 2.07, 811 | 3.58, 812 | 2.48, 813 | 1.54, 814 | 1.46, 815 | 2.00, 816 | 1.35, 817 | 5.58, 818 | 0.57, 819 | 0.23, 820 | 0.24, 821 | 0.20, 822 | 0.32, 823 | 0.16, 824 | 0.09, 825 | 0.00, 826 | 0.00, 827 | 0.00, 828 | 0.00, 829 | 0.00, 830 | ) 831 | ) 832 | 833 | REFERENCE_ILLUM_BLACKBODY = numpy.array( 834 | ( 835 | 43.36, 836 | 47.77, 837 | 52.15, 838 | 56.44, 839 | 60.62, 840 | 64.65, 841 | 68.51, 842 | 72.18, 843 | 75.63, 844 | 78.87, 845 | 81.87, 846 | 84.63, 847 | 87.16, 848 | 89.44, 849 | 91.48, 850 | 93.29, 851 | 94.87, 852 | 96.23, 853 | 97.37, 854 | 98.31, 855 | 99.05, 856 | 99.61, 857 | 100.00, 858 | 100.22, 859 | 100.29, 860 | 100.21, 861 | 100.00, 862 | 99.67, 863 | 99.22, 864 | 98.67, 865 | 98.02, 866 | 97.28, 867 | 96.47, 868 | 95.58, 869 | 94.63, 870 | 93.62, 871 | 92.56, 872 | 91.45, 873 | 90.30, 874 | 89.12, 875 | 87.91, 876 | 86.67, 877 | 85.42, 878 | 84.15, 879 | 82.86, 880 | 81.56, 881 | 80.26, 882 | 78.95, 883 | 77.64, 884 | 76.33, 885 | ) 886 | ) 887 | 888 | # This table is used to match up illuminants to spectral distributions above. 889 | # It should correspond to a ColorObject.illuminant attribute. 890 | REF_ILLUM_TABLE = { 891 | "a": REFERENCE_ILLUM_A, 892 | "b": REFERENCE_ILLUM_B, 893 | "c": REFERENCE_ILLUM_C, 894 | "d50": REFERENCE_ILLUM_D50, 895 | "d65": REFERENCE_ILLUM_D65, 896 | "e": REFERENCE_ILLUM_E, 897 | "f2": REFERENCE_ILLUM_F2, 898 | "f7": REFERENCE_ILLUM_F7, 899 | "f11": REFERENCE_ILLUM_F11, 900 | "blackbody": REFERENCE_ILLUM_BLACKBODY, 901 | } 902 | -------------------------------------------------------------------------------- /doc_src/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | 49 | clean: 50 | rm -rf $(BUILDDIR)/* 51 | 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | dirhtml: 58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 61 | 62 | singlehtml: 63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 64 | @echo 65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 66 | 67 | pickle: 68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 69 | @echo 70 | @echo "Build finished; now you can process the pickle files." 71 | 72 | json: 73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 74 | @echo 75 | @echo "Build finished; now you can process the JSON files." 76 | 77 | htmlhelp: 78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 79 | @echo 80 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 81 | ".hhp project file in $(BUILDDIR)/htmlhelp." 82 | 83 | qthelp: 84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 85 | @echo 86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/python-colormath.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/python-colormath.qhc" 91 | 92 | devhelp: 93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 94 | @echo 95 | @echo "Build finished." 96 | @echo "To view the help file:" 97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/python-colormath" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/python-colormath" 99 | @echo "# devhelp" 100 | 101 | epub: 102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 103 | @echo 104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 105 | 106 | latex: 107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 108 | @echo 109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 111 | "(use \`make latexpdf' here to do that automatically)." 112 | 113 | latexpdf: 114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 115 | @echo "Running LaTeX files through pdflatex..." 116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 118 | 119 | latexpdfja: 120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 121 | @echo "Running LaTeX files through platex and dvipdfmx..." 122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 124 | 125 | text: 126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 127 | @echo 128 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 129 | 130 | man: 131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 132 | @echo 133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 134 | 135 | texinfo: 136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 137 | @echo 138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 139 | @echo "Run \`make' in that directory to run these through makeinfo" \ 140 | "(use \`make info' here to do that automatically)." 141 | 142 | info: 143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 144 | @echo "Running Texinfo files through makeinfo..." 145 | make -C $(BUILDDIR)/texinfo info 146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 147 | 148 | gettext: 149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 150 | @echo 151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 152 | 153 | changes: 154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 155 | @echo 156 | @echo "The overview file is in $(BUILDDIR)/changes." 157 | 158 | linkcheck: 159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 160 | @echo 161 | @echo "Link check complete; look for any errors in the above output " \ 162 | "or in $(BUILDDIR)/linkcheck/output.txt." 163 | 164 | doctest: 165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 166 | @echo "Testing of doctests in the sources finished, look at the " \ 167 | "results in $(BUILDDIR)/doctest/output.txt." 168 | 169 | xml: 170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 171 | @echo 172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 173 | 174 | pseudoxml: 175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 176 | @echo 177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 178 | -------------------------------------------------------------------------------- /doc_src/_build/.GIT_FOO: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gtaylor/python-colormath/6d3261f9c8a2449bdffdf563eb541c8b6846af2d/doc_src/_build/.GIT_FOO -------------------------------------------------------------------------------- /doc_src/_static/.GIT_FOO: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gtaylor/python-colormath/6d3261f9c8a2449bdffdf563eb541c8b6846af2d/doc_src/_static/.GIT_FOO -------------------------------------------------------------------------------- /doc_src/_templates/.GIT_FOO: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gtaylor/python-colormath/6d3261f9c8a2449bdffdf563eb541c8b6846af2d/doc_src/_templates/.GIT_FOO -------------------------------------------------------------------------------- /doc_src/color_appearance_models.rst: -------------------------------------------------------------------------------- 1 | .. _color-appearance-models: 2 | 3 | .. include:: global.txt 4 | 5 | Color Appearance Models 6 | ======================= 7 | Color appearance models allow the prediction of perceptual correlates (e.g., lightness, chroma or hue) of a given 8 | surface color under certain viewing conditions (e.g., a certain illuminant, surround or background). The complexity of 9 | color appearance models can range from very low, e.g., CIELAB can technically be considered a color appearance model, 10 | to very complex models that take into account a large number color appearance phenomena. 11 | 12 | Each of the classes in this module represents a specific model and its computation, yielding the predicted perceptual 13 | correlates as instance attributes. 14 | Discussing the details of each model go beyond this documentation, but we provide references to the relevant 15 | literature for each model and would advice familiarising yourself with it, before using a given models. 16 | 17 | 18 | Example 19 | ------- 20 | 21 | .. code-block:: python 22 | 23 | # Color stimulus 24 | color = XYZColor(19.01, 20, 21.78) 25 | 26 | # The two illuminants that will be compared. 27 | illuminant_d65 = XYZColor(95.05, 100, 108.88) 28 | illuminant_a = XYZColor(109.85, 100, 35.58) 29 | 30 | # Background relative luminance 31 | y_b = 20 32 | 33 | # Adapting luminance 34 | l_a = 328.31 35 | 36 | # Surround condition assumed to be average (see CIECAM02 documentation for values) 37 | c = 0.69 38 | n_c = 1 39 | f = 1 40 | 41 | model = CIECAM02(color.xyz_x, color.xyz_y, color.xyz_z, 42 | illuminant_d65.xyz_x, illuminant_d65.xyz_y, illuminant_d65.xyz_z, 43 | y_b, l_a, c, n_c, f) 44 | 45 | 46 | Nayatani95 et al. Model 47 | ------------------------ 48 | 49 | .. autoclass:: colormath.color_appearance_models.Nayatani95 50 | 51 | Hunt Model 52 | ----------- 53 | 54 | .. autoclass:: colormath.color_appearance_models.Hunt 55 | :exclude-members: adjust_white_for_scc 56 | 57 | RLAB Model 58 | ---------- 59 | 60 | .. autoclass:: colormath.color_appearance_models.RLAB 61 | 62 | ATD95 Model 63 | ------------ 64 | 65 | .. autoclass:: colormath.color_appearance_models.ATD95 66 | 67 | LLAB Model 68 | ----------- 69 | 70 | .. autoclass:: colormath.color_appearance_models.LLAB 71 | 72 | CIECAM02 Model 73 | --------------- 74 | 75 | .. autoclass:: colormath.color_appearance_models.CIECAM02 76 | 77 | CIECAM02-m1 Model 78 | ------------------ 79 | 80 | .. autoclass:: colormath.color_appearance_models.CIECAM02m1 81 | 82 | -------------------------------------------------------------------------------- /doc_src/color_objects.rst: -------------------------------------------------------------------------------- 1 | .. _color-objects: 2 | 3 | .. include:: global.txt 4 | 5 | Color Objects 6 | ============= 7 | 8 | python-colormath has support for many of the commonly used color spaces. These 9 | are represented by Color objects. 10 | 11 | SpectralColor 12 | ------------- 13 | 14 | .. autoclass:: colormath.color_objects.SpectralColor 15 | 16 | LabColor 17 | -------- 18 | 19 | .. autoclass:: colormath.color_objects.LabColor 20 | 21 | LCHabColor 22 | ---------- 23 | 24 | .. autoclass:: colormath.color_objects.LCHabColor 25 | 26 | LCHuvColor 27 | ---------- 28 | 29 | .. autoclass:: colormath.color_objects.LCHuvColor 30 | 31 | LuvColor 32 | -------- 33 | 34 | .. autoclass:: colormath.color_objects.LuvColor 35 | 36 | XYZColor 37 | -------- 38 | 39 | .. autoclass:: colormath.color_objects.XYZColor 40 | 41 | xyYColor 42 | -------- 43 | 44 | .. autoclass:: colormath.color_objects.xyYColor 45 | 46 | sRGBColor 47 | --------- 48 | 49 | .. autoclass:: colormath.color_objects.sRGBColor 50 | 51 | BT2020Color 52 | ----------- 53 | 54 | .. autoclass:: colormath.color_objects.BT2020Color 55 | 56 | AdobeRGBColor 57 | ------------- 58 | 59 | .. autoclass:: colormath.color_objects.AdobeRGBColor 60 | 61 | HSLColor 62 | -------- 63 | 64 | .. autoclass:: colormath.color_objects.HSLColor 65 | 66 | HSVColor 67 | -------- 68 | 69 | .. autoclass:: colormath.color_objects.HSVColor 70 | 71 | CMYColor 72 | -------- 73 | 74 | .. autoclass:: colormath.color_objects.CMYColor 75 | 76 | CMYKColor 77 | --------- 78 | 79 | .. autoclass:: colormath.color_objects.CMYKColor 80 | 81 | IPTColor 82 | --------- 83 | 84 | .. autoclass:: colormath.color_objects.IPTColor 85 | -------------------------------------------------------------------------------- /doc_src/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # python-colormath documentation build configuration file, created by 4 | # sphinx-quickstart on Thu Mar 20 00:32:55 2014. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import sys 16 | import os 17 | 18 | # If extensions (or modules to document with autodoc) are in another directory, 19 | # add these directories to sys.path here. If the directory is relative to the 20 | # documentation root, use os.path.abspath to make it absolute, like shown here. 21 | project_root = os.path.dirname(os.path.abspath(".")) 22 | sys.path.insert(0, project_root) 23 | 24 | # -- General configuration ------------------------------------------------ 25 | 26 | # If your documentation needs a minimal Sphinx version, state it here. 27 | # needs_sphinx = '1.0' 28 | 29 | # Add any Sphinx extension module names here, as strings. They can be 30 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 31 | # ones. 32 | extensions = [ 33 | "sphinx.ext.autodoc", 34 | "sphinx.ext.intersphinx", 35 | "sphinx.ext.viewcode", 36 | "sphinx.ext.mathjax", 37 | ] 38 | 39 | # Add any paths that contain templates here, relative to this directory. 40 | templates_path = ["_templates"] 41 | 42 | # The suffix of source filenames. 43 | source_suffix = ".rst" 44 | 45 | # The encoding of source files. 46 | # source_encoding = 'utf-8-sig' 47 | 48 | # The master toctree document. 49 | master_doc = "index" 50 | 51 | # General information about the project. 52 | project = u"python-colormath" 53 | copyright = u"2014, Greg Taylor" 54 | 55 | # The version info for the project you're documenting, acts as replacement for 56 | # |version| and |release|, also used in various other places throughout the 57 | # built documents. 58 | # 59 | # The short X.Y version. 60 | version = "3.0" 61 | # The full version, including alpha/beta/rc tags. 62 | release = "3.0.0" 63 | 64 | # The language for content autogenerated by Sphinx. Refer to documentation 65 | # for a list of supported languages. 66 | # language = None 67 | 68 | # There are two options for replacing |today|: either, you set today to some 69 | # non-false value, then it is used: 70 | # today = '' 71 | # Else, today_fmt is used as the format for a strftime call. 72 | # today_fmt = '%B %d, %Y' 73 | 74 | # List of patterns, relative to source directory, that match files and 75 | # directories to ignore when looking for source files. 76 | exclude_patterns = ["_build"] 77 | 78 | # The reST default role (used for this markup: `text`) to use for all 79 | # documents. 80 | # default_role = None 81 | 82 | # If true, '()' will be appended to :func: etc. cross-reference text. 83 | # add_function_parentheses = True 84 | 85 | # If true, the current module name will be prepended to all description 86 | # unit titles (such as .. function::). 87 | # add_module_names = True 88 | 89 | # If true, sectionauthor and moduleauthor directives will be shown in the 90 | # output. They are ignored by default. 91 | # show_authors = False 92 | 93 | # The name of the Pygments (syntax highlighting) style to use. 94 | pygments_style = "sphinx" 95 | 96 | # A list of ignored prefixes for module index sorting. 97 | # modindex_common_prefix = [] 98 | 99 | # If true, keep warnings as "system message" paragraphs in the built documents. 100 | # keep_warnings = False 101 | 102 | 103 | # -- Options for HTML output ---------------------------------------------- 104 | 105 | # The theme to use for HTML and HTML Help pages. See the documentation for 106 | # a list of builtin themes. 107 | html_theme = "default" 108 | 109 | # Theme options are theme-specific and customize the look and feel of a theme 110 | # further. For a list of options available for each theme, see the 111 | # documentation. 112 | # html_theme_options = {} 113 | 114 | # Add any paths that contain custom themes here, relative to this directory. 115 | # html_theme_path = [] 116 | 117 | # The name for this set of Sphinx documents. If None, it defaults to 118 | # " v documentation". 119 | # html_title = None 120 | 121 | # A shorter title for the navigation bar. Default is the same as html_title. 122 | # html_short_title = None 123 | 124 | # The name of an image file (relative to this directory) to place at the top 125 | # of the sidebar. 126 | # html_logo = None 127 | 128 | # The name of an image file (within the static path) to use as favicon of the 129 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 130 | # pixels large. 131 | # html_favicon = None 132 | 133 | # Add any paths that contain custom static files (such as style sheets) here, 134 | # relative to this directory. They are copied after the builtin static files, 135 | # so a file named "default.css" will overwrite the builtin "default.css". 136 | html_static_path = ["_static"] 137 | 138 | # Add any extra paths that contain custom files (such as robots.txt or 139 | # .htaccess) here, relative to this directory. These files are copied 140 | # directly to the root of the documentation. 141 | # html_extra_path = [] 142 | 143 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 144 | # using the given strftime format. 145 | # html_last_updated_fmt = '%b %d, %Y' 146 | 147 | # If true, SmartyPants will be used to convert quotes and dashes to 148 | # typographically correct entities. 149 | # html_use_smartypants = True 150 | 151 | # Custom sidebar templates, maps document names to template names. 152 | # html_sidebars = {} 153 | 154 | # Additional templates that should be rendered to pages, maps page names to 155 | # template names. 156 | # html_additional_pages = {} 157 | 158 | # If false, no module index is generated. 159 | # html_domain_indices = True 160 | 161 | # If false, no index is generated. 162 | # html_use_index = True 163 | 164 | # If true, the index is split into individual pages for each letter. 165 | # html_split_index = False 166 | 167 | # If true, links to the reST sources are added to the pages. 168 | # html_show_sourcelink = True 169 | 170 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 171 | # html_show_sphinx = True 172 | 173 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 174 | # html_show_copyright = True 175 | 176 | # If true, an OpenSearch description file will be output, and all pages will 177 | # contain a tag referring to it. The value of this option must be the 178 | # base URL from which the finished HTML is served. 179 | # html_use_opensearch = '' 180 | 181 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 182 | # html_file_suffix = None 183 | 184 | # Output file base name for HTML help builder. 185 | htmlhelp_basename = "python-colormathdoc" 186 | 187 | 188 | # -- Options for LaTeX output --------------------------------------------- 189 | 190 | latex_elements = { 191 | # The paper size ('letterpaper' or 'a4paper'). 192 | # 'papersize': 'letterpaper', 193 | # The font size ('10pt', '11pt' or '12pt'). 194 | # 'pointsize': '10pt', 195 | # Additional stuff for the LaTeX preamble. 196 | # 'preamble': '', 197 | } 198 | 199 | # Grouping the document tree into LaTeX files. List of tuples 200 | # (source start file, target name, title, 201 | # author, documentclass [howto, manual, or own class]). 202 | latex_documents = [ 203 | ( 204 | "index", 205 | "python-colormath.tex", 206 | u"python-colormath Documentation", 207 | u"Greg Taylor", 208 | "manual", 209 | ), 210 | ] 211 | 212 | # The name of an image file (relative to this directory) to place at the top of 213 | # the title page. 214 | # latex_logo = None 215 | 216 | # For "manual" documents, if this is true, then toplevel headings are parts, 217 | # not chapters. 218 | # latex_use_parts = False 219 | 220 | # If true, show page references after internal links. 221 | # latex_show_pagerefs = False 222 | 223 | # If true, show URL addresses after external links. 224 | # latex_show_urls = False 225 | 226 | # Documents to append as an appendix to all manuals. 227 | # latex_appendices = [] 228 | 229 | # If false, no module index is generated. 230 | # latex_domain_indices = True 231 | 232 | 233 | # -- Options for manual page output --------------------------------------- 234 | 235 | # One entry per manual page. List of tuples 236 | # (source start file, name, description, authors, manual section). 237 | man_pages = [ 238 | ( 239 | "index", 240 | "python-colormath", 241 | u"python-colormath Documentation", 242 | [u"Greg Taylor"], 243 | 1, 244 | ) 245 | ] 246 | 247 | # If true, show URL addresses after external links. 248 | # man_show_urls = False 249 | 250 | 251 | # -- Options for Texinfo output ------------------------------------------- 252 | 253 | # Grouping the document tree into Texinfo files. List of tuples 254 | # (source start file, target name, title, author, 255 | # dir menu entry, description, category) 256 | texinfo_documents = [ 257 | ( 258 | "index", 259 | "python-colormath", 260 | u"python-colormath Documentation", 261 | u"Greg Taylor", 262 | "python-colormath", 263 | "One line description of project.", 264 | "Miscellaneous", 265 | ), 266 | ] 267 | 268 | # Documents to append as an appendix to all manuals. 269 | # texinfo_appendices = [] 270 | 271 | # If false, no module index is generated. 272 | # texinfo_domain_indices = True 273 | 274 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 275 | # texinfo_show_urls = 'footnote' 276 | 277 | # If true, do not generate a @detailmenu in the "Top" node's menu. 278 | # texinfo_no_detailmenu = False 279 | 280 | 281 | # Example configuration for intersphinx: refer to the Python standard library. 282 | intersphinx_mapping = {"http://docs.python.org/": None} 283 | 284 | autoclass_content = "both" 285 | autodoc_default_flags = ["members", "inherited-members", "show-inheritance"] 286 | autodoc_member_order = "groupwise" 287 | -------------------------------------------------------------------------------- /doc_src/conversions.rst: -------------------------------------------------------------------------------- 1 | .. _conversions: 2 | 3 | .. include:: global.txt 4 | 5 | Color Conversions 6 | ================= 7 | 8 | Converting between color spaces is very simple with python-colormath. To see 9 | a full list of supported color spaces, see :doc:`color_objects`. 10 | 11 | All conversions happen through the ``convert_color`` function shown below. 12 | The original Color instance is passed in as the first argument, and the 13 | desired Color class (not an instance) is passed in as the second argument. 14 | If the conversion can be made, a new Color instance will be returned. 15 | 16 | .. autofunction:: colormath.color_conversions.convert_color 17 | 18 | Example 19 | ------- 20 | 21 | This is a simple example of a CIE Lab to CIE XYZ conversion. Refer to 22 | :doc:`color_objects` for a full list of different color spaces that can 23 | be instantiated and converted between. 24 | 25 | .. code-block:: python 26 | 27 | from colormath.color_objects import LabColor, XYZColor 28 | from colormath.color_conversions import convert_color 29 | 30 | lab = LabColor(0.903, 16.296, -2.22) 31 | xyz = convert_color(lab, XYZColor) 32 | 33 | Some color spaces require a trip through RGB during conversion. For example, 34 | to get from XYZ to HSL, we have to convert XYZ->RGB->HSL. The same could 35 | be said for XYZ to CMYK (XYZ->RGB->CMY->CMYK). Different RGB color spaces 36 | have different gamut sizes and capabilities, which can affect your 37 | converted color values. 38 | 39 | sRGB is the default RGB color space due to its ubiquity. If you would like 40 | to use a different RGB space for a conversion, you can do something like this: 41 | 42 | .. code-block:: python 43 | 44 | from colormath.color_objects import XYZColor, HSLColor, AdobeRGBColor 45 | from colormath.color_conversions import convert_color 46 | 47 | xyz = XYZColor(0.1, 0.2, 0.3) 48 | hsl = convert_color(xyz, HSLColor, through_rgb_type=AdobeRGBColor) 49 | # If you are going to convert back to XYZ, make sure you use the same 50 | # RGB color space on the way back. 51 | xyz2 = convert_color(hsl, XYZColor, through_rgb_type=AdobeRGBColor) 52 | 53 | RGB conversions and native illuminants 54 | -------------------------------------- 55 | 56 | When converting RGB colors to any of the CIE spaces, we have to pass through 57 | the XYZ color space. This serves as a crossroads for conversions to basically 58 | all of the reflective color spaces (CIE Lab, LCH, Luv, etc). The RGB spaces 59 | are reflective, where the illumination is provided. In the case of a reflective 60 | space like XYZ, the illuminant must be supplied by a light source. 61 | 62 | Each RGB space has its own native illuminant, which can vary from space 63 | to space. To see some of these for yourself, check out 64 | Bruce Lindbloom's `XYZ to RGB matrices `_. 65 | 66 | 67 | To cite the most commonly used RGB color space as an example, sRGB has a 68 | native illuminant of D65. When we convert RGB to XYZ, that native illuminant 69 | carries over unless explicitly overridden. If you aren't expecting this behavior, 70 | you'll end up with variations in your converted color's numbers. 71 | 72 | To explicitly request a specific illuminant, provide the ``target_illuminant`` 73 | keyword when using :py:func:`colormath.color_conversions.convert_color`. 74 | 75 | .. code-block:: python 76 | 77 | from colormath.color_objects import XYZColor, sRGBColor 78 | from colormath.color_conversions import convert_color 79 | 80 | rgb = RGBColor(0.1, 0.2, 0.3) 81 | xyz = convert_color(rgb, XYZColor, target_illuminant='d50') 82 | 83 | RGB conversions and out-of-gamut coordinates 84 | -------------------------------------------- 85 | 86 | RGB spaces tend to have a smaller gamut than some of the CIE color spaces. 87 | When converting to RGB, this can cause some of the coordinates to end up 88 | being out of the acceptable range (0.0-1.0 or 0-255, depending on whether 89 | your RGB color is upscaled). 90 | 91 | Rather than clamp these for you, we leave them as-is. This allows for more 92 | accurate conversions back to the CIE color spaces. If you require the clamped 93 | (0.0-1.0 or 0-255) values, use the following properties on any RGB color: 94 | 95 | * ``clamped_rgb_r`` 96 | * ``clamped_rgb_g`` 97 | * ``clamped_rgb_b`` 98 | -------------------------------------------------------------------------------- /doc_src/delta_e.rst: -------------------------------------------------------------------------------- 1 | .. _delta-e: 2 | 3 | .. include:: global.txt 4 | 5 | Delta E Equations 6 | ================= 7 | 8 | Delta E equations are used to put a number on the visual difference between two 9 | :py:class:`LabColor ` instances. While 10 | different lighting conditions, substrates, and physical condition can all 11 | introduce unexpected variables, these equations are a good rough starting point 12 | for comparing colors. 13 | 14 | Each of the following Delta E functions has different characteristics. Some may 15 | be more suitable for certain applications than others. While it's outside the 16 | scope of this module's documentation to go into much detail, we link to 17 | relevant material when possible. 18 | 19 | Example 20 | ------- 21 | 22 | .. code-block:: python 23 | 24 | from colormath.color_objects import LabColor 25 | from colormath.color_diff import delta_e_cie1976 26 | 27 | # Reference color. 28 | color1 = LabColor(lab_l=0.9, lab_a=16.3, lab_b=-2.22) 29 | # Color to be compared to the reference. 30 | color2 = LabColor(lab_l=0.7, lab_a=14.2, lab_b=-1.80) 31 | # This is your delta E value as a float. 32 | delta_e = delta_e_cie1976(color1, color2) 33 | 34 | Delta E CIE 1976 35 | ---------------- 36 | 37 | .. autofunction:: colormath.color_diff.delta_e_cie1976 38 | 39 | 40 | Delta E CIE 1994 41 | ---------------- 42 | 43 | .. autofunction:: colormath.color_diff.delta_e_cie1994 44 | 45 | Delta E CIE 2000 46 | ---------------- 47 | 48 | .. autofunction:: colormath.color_diff.delta_e_cie2000 49 | 50 | Delta E CMC 51 | ----------- 52 | 53 | .. autofunction:: colormath.color_diff.delta_e_cmc 54 | -------------------------------------------------------------------------------- /doc_src/density.rst: -------------------------------------------------------------------------------- 1 | .. _density: 2 | 3 | .. include:: global.txt 4 | 5 | ANSI and ISO Density 6 | ==================== 7 | 8 | Density may be calculated from 9 | :py:class:`LabColor ` instances. 10 | 11 | .. autofunction:: colormath.density.auto_density 12 | 13 | .. autofunction:: colormath.density.ansi_density 14 | 15 | Example 16 | ------- 17 | 18 | .. code-block:: python 19 | 20 | from colormath.color_objects import SpectralColor 21 | from colormath.density import auto_density, ansi_density 22 | from colormath.density_standards import ANSI_STATUS_T_RED 23 | 24 | # Omitted the full spectral kwargs for brevity. 25 | color = SpectralColor(spec_340nm=0.08, ...) 26 | # ANSI T Density for the spectral color. 27 | density = auto_density(color) 28 | 29 | # Or maybe we want to specify which filter to use. 30 | red_density = ansi_density(color, ANSI_STATUS_T_RED) 31 | 32 | Valid Density Constants 33 | ----------------------- 34 | 35 | The following density constants within :py:mod:`colormath.density_standards` 36 | can be passed to :py:func:`colormath.density.ansi_density`: 37 | 38 | * ``ANSI_STATUS_A_RED`` 39 | * ``ANSI_STATUS_A_GREEN`` 40 | * ``ANSI_STATUS_A_BLUE`` 41 | * ``ANSI_STATUS_E_RED`` 42 | * ``ANSI_STATUS_E_GREEN`` 43 | * ``ANSI_STATUS_E_BLUE`` 44 | * ``ANSI_STATUS_M_RED`` 45 | * ``ANSI_STATUS_M_GREEN`` 46 | * ``ANSI_STATUS_M_BLUE`` 47 | * ``ANSI_STATUS_T_RED`` 48 | * ``ANSI_STATUS_T_GREEN`` 49 | * ``ANSI_STATUS_T_BLUE`` 50 | * ``TYPE1`` 51 | * ``TYPE2`` 52 | * ``ISO_VISUAL`` 53 | -------------------------------------------------------------------------------- /doc_src/global.txt: -------------------------------------------------------------------------------- 1 | .. _GitHub project: https://github.com/gtaylor/python-colormath 2 | .. _issue tracker: https://github.com/gtaylor/python-colormath/issues 3 | .. _@gctaylor Twitter: https://twitter.com/#!/gctaylor 4 | 5 | .. _Python: http://python.org 6 | .. _nose: http://somethingaboutorange.com/mrl/projects/nose/ 7 | .. _NumPy: http://www.numpy.org/ 8 | .. _networkx: https://networkx.github.io/ 9 | 10 | .. _virtualenv: http://pypi.python.org/pypi/virtualenv 11 | .. _virtualenvwrapper: http://www.doughellmann.com/projects/virtualenvwrapper/ 12 | 13 | .. _git: http://git-scm.com/ 14 | .. _GitHub: https://github.com/ 15 | 16 | .. _BSD License: http://opensource.org/licenses/bsd-license.php 17 | -------------------------------------------------------------------------------- /doc_src/illuminants.rst: -------------------------------------------------------------------------------- 1 | .. _illuminants: 2 | 3 | .. include:: global.txt 4 | 5 | Observers and Illuminants 6 | ========================= 7 | 8 | Illuminants and observer angles are used in all color spaces that use 9 | reflective (instead of transmissive) light. Here are a few brief overviews 10 | of what these are and what they do: 11 | 12 | * `Understanding Standard Illuminants in Color Measurement`_ - Konica Minolta 13 | * `What is Meant by the Term "Observer Angle"?`_ - XRite 14 | 15 | To adjust the illuminants and/or observer angles on a color:: 16 | 17 | lab = LabColor(0.1, 0.2, 0.3, observer='10', illuminant='d65') 18 | 19 | Two-degree observer angle 20 | ------------------------- 21 | 22 | These illuminants can be used with ``observer='2'``, for the color spaces 23 | that require illuminant/observer: 24 | 25 | * ``'a'`` 26 | * ``'b'`` 27 | * ``'c'`` 28 | * ``'d50'`` 29 | * ``'d55'`` 30 | * ``'d65'`` 31 | * ``'d75'`` 32 | * ``'e'`` 33 | * ``'f2'`` 34 | * ``'f7'`` 35 | * ``'f11'`` 36 | 37 | Ten-degree observer angle 38 | ------------------------- 39 | 40 | These illuminants can be used with ``observer='10'``, for the color spaces 41 | that require illuminant/observer: 42 | 43 | * ``'d50'`` 44 | * ``'d55'`` 45 | * ``'d65'`` 46 | * ``'d75'`` 47 | 48 | .. _Understanding Standard Illuminants in Color Measurement: http://sensing.konicaminolta.us/2013/11/Understanding-Standard-Illuminants-in-Color-Measurement/ 49 | .. _What is Meant by the Term "Observer Angle"?: http://www.xrite.com/product_overview.aspx?ID=773&Action=support&SupportID=3544 50 | -------------------------------------------------------------------------------- /doc_src/index.rst: -------------------------------------------------------------------------------- 1 | .. _index: 2 | 3 | .. include:: global.txt 4 | 5 | python-colormath 6 | ================ 7 | 8 | python-colormath is a simple Python module that spares the user from directly 9 | dealing with 10 | `color math `_. 11 | Some features include: 12 | 13 | * Support for a wide range of color spaces. A good chunk of the CIE spaces, 14 | RGB, HSL/HSV, CMY/CMYK, and many more. 15 | * :doc:`Conversions ` between the various color spaces. 16 | For example, XYZ to sRGB, Spectral to XYZ, CIE Lab to Adobe RGB. 17 | * Calculation of :doc:`color difference `. All CIE Delta E functions, 18 | plus CMC. 19 | * Chromatic adaptations (changing illuminants). 20 | * RGB to hex and vice-versa. 21 | * 16-bit RGB support. 22 | * Runs on Python 2.7 and Python 3.3+. 23 | 24 | **License:** python-colormath is licensed under the `BSD License`_. 25 | 26 | Assorted Info 27 | ------------- 28 | 29 | * `Issue tracker`_ - Report bugs, ask questions, and share ideas here. 30 | * `GitHub project`_ - Source code and issue tracking. 31 | * `@gctaylor Twitter`_ - Tweets from the maintainer. 32 | * `Greg Taylor's blog `_ - Occasional posts about 33 | color math and software development. 34 | 35 | Topics 36 | ------ 37 | 38 | .. toctree:: 39 | :maxdepth: 2 40 | 41 | installation 42 | color_objects 43 | illuminants 44 | conversions 45 | delta_e 46 | density 47 | color_appearance_models 48 | 49 | .. toctree:: 50 | :maxdepth: 1 51 | 52 | release_notes 53 | 54 | Useful color math resources 55 | --------------------------- 56 | 57 | * `Bruce Lindbloom`_ - Lots of formulas, calculators, and standards. 58 | * `John the Math Guy`_ - Useful tutorials and explanations of color theory. 59 | 60 | .. _Bruce Lindbloom: http://www.brucelindbloom.com/ 61 | .. _John the Math Guy: http://johnthemathguy.blogspot.com/ 62 | -------------------------------------------------------------------------------- /doc_src/installation.rst: -------------------------------------------------------------------------------- 1 | .. _installation: 2 | 3 | .. include:: global.txt 4 | 5 | Installation 6 | ============ 7 | 8 | python-colormath currently requires Python 2.7 or Python 3.3+. There are no 9 | plans to add support for earlier versions of Python 2 or 3. The only other 10 | requirements are NumPy_ and networkx_. 11 | 12 | For those on Linux/Unix Mac OS, the easiest route will be :command:`pip` or 13 | :command:`easy_install`:: 14 | 15 | pip install colormath 16 | 17 | If you are on Windows, you'll need to visit NumPy_, download their binary 18 | distribution, then install colormath. 19 | -------------------------------------------------------------------------------- /doc_src/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | goto end 41 | ) 42 | 43 | if "%1" == "clean" ( 44 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 45 | del /q /s %BUILDDIR%\* 46 | goto end 47 | ) 48 | 49 | 50 | %SPHINXBUILD% 2> nul 51 | if errorlevel 9009 ( 52 | echo. 53 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 54 | echo.installed, then set the SPHINXBUILD environment variable to point 55 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 56 | echo.may add the Sphinx directory to PATH. 57 | echo. 58 | echo.If you don't have Sphinx installed, grab it from 59 | echo.http://sphinx-doc.org/ 60 | exit /b 1 61 | ) 62 | 63 | if "%1" == "html" ( 64 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 68 | goto end 69 | ) 70 | 71 | if "%1" == "dirhtml" ( 72 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 76 | goto end 77 | ) 78 | 79 | if "%1" == "singlehtml" ( 80 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 84 | goto end 85 | ) 86 | 87 | if "%1" == "pickle" ( 88 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can process the pickle files. 92 | goto end 93 | ) 94 | 95 | if "%1" == "json" ( 96 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 97 | if errorlevel 1 exit /b 1 98 | echo. 99 | echo.Build finished; now you can process the JSON files. 100 | goto end 101 | ) 102 | 103 | if "%1" == "htmlhelp" ( 104 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 105 | if errorlevel 1 exit /b 1 106 | echo. 107 | echo.Build finished; now you can run HTML Help Workshop with the ^ 108 | .hhp project file in %BUILDDIR%/htmlhelp. 109 | goto end 110 | ) 111 | 112 | if "%1" == "qthelp" ( 113 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 114 | if errorlevel 1 exit /b 1 115 | echo. 116 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 117 | .qhcp project file in %BUILDDIR%/qthelp, like this: 118 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\python-colormath.qhcp 119 | echo.To view the help file: 120 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\python-colormath.ghc 121 | goto end 122 | ) 123 | 124 | if "%1" == "devhelp" ( 125 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished. 129 | goto end 130 | ) 131 | 132 | if "%1" == "epub" ( 133 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 137 | goto end 138 | ) 139 | 140 | if "%1" == "latex" ( 141 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 145 | goto end 146 | ) 147 | 148 | if "%1" == "latexpdf" ( 149 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 150 | cd %BUILDDIR%/latex 151 | make all-pdf 152 | cd %BUILDDIR%/.. 153 | echo. 154 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 155 | goto end 156 | ) 157 | 158 | if "%1" == "latexpdfja" ( 159 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 160 | cd %BUILDDIR%/latex 161 | make all-pdf-ja 162 | cd %BUILDDIR%/.. 163 | echo. 164 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 165 | goto end 166 | ) 167 | 168 | if "%1" == "text" ( 169 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 170 | if errorlevel 1 exit /b 1 171 | echo. 172 | echo.Build finished. The text files are in %BUILDDIR%/text. 173 | goto end 174 | ) 175 | 176 | if "%1" == "man" ( 177 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 178 | if errorlevel 1 exit /b 1 179 | echo. 180 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 181 | goto end 182 | ) 183 | 184 | if "%1" == "texinfo" ( 185 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 186 | if errorlevel 1 exit /b 1 187 | echo. 188 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 189 | goto end 190 | ) 191 | 192 | if "%1" == "gettext" ( 193 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 194 | if errorlevel 1 exit /b 1 195 | echo. 196 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 197 | goto end 198 | ) 199 | 200 | if "%1" == "changes" ( 201 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 202 | if errorlevel 1 exit /b 1 203 | echo. 204 | echo.The overview file is in %BUILDDIR%/changes. 205 | goto end 206 | ) 207 | 208 | if "%1" == "linkcheck" ( 209 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 210 | if errorlevel 1 exit /b 1 211 | echo. 212 | echo.Link check complete; look for any errors in the above output ^ 213 | or in %BUILDDIR%/linkcheck/output.txt. 214 | goto end 215 | ) 216 | 217 | if "%1" == "doctest" ( 218 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 219 | if errorlevel 1 exit /b 1 220 | echo. 221 | echo.Testing of doctests in the sources finished, look at the ^ 222 | results in %BUILDDIR%/doctest/output.txt. 223 | goto end 224 | ) 225 | 226 | if "%1" == "xml" ( 227 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 228 | if errorlevel 1 exit /b 1 229 | echo. 230 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 231 | goto end 232 | ) 233 | 234 | if "%1" == "pseudoxml" ( 235 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 236 | if errorlevel 1 exit /b 1 237 | echo. 238 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 239 | goto end 240 | ) 241 | 242 | :end 243 | -------------------------------------------------------------------------------- /doc_src/release_notes.rst: -------------------------------------------------------------------------------- 1 | .. _release_notes: 2 | 3 | .. include:: global.txt 4 | 5 | Release Notes 6 | ============= 7 | 8 | 3.0.0 9 | ----- 10 | 11 | Features 12 | ^^^^^^^^ 13 | 14 | * Python 3.5 and 3.6 are now supported. 15 | 16 | Backwards-Incompatible Changes 17 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 18 | 19 | * Python 3.3 and 3.4 are no longer supported. 20 | * networkx>=2.0 is now required. 21 | 22 | Bug Fixes 23 | ^^^^^^^^^ 24 | 25 | * Add NodeNotFound to GraphConversionManager.get_conversion_path(). 26 | 27 | 2.2.0 28 | ----- 29 | 30 | Features 31 | ^^^^^^^^ 32 | 33 | * AppleRGBColor added. (jan-warchol) 34 | * Added compatibility with NetworkX 2.0. (Erotemic) 35 | 36 | Bug Fixes 37 | ^^^^^^^^^ 38 | 39 | * `IPT_to_XYZ` docstring corrected. (mumbleskates) 40 | 41 | 2.1.1 42 | ----- 43 | 44 | Bug Fixes 45 | ^^^^^^^^^ 46 | 47 | * Add ``network`` to install_requires. (Ed-von-Schleck) 48 | 49 | 2.1.0 50 | ----- 51 | 52 | Features 53 | ^^^^^^^^ 54 | 55 | * Added new NetworkX graph-based resolution of conversions between 56 | color spaces. Reduces boilerplate and makes it much easier to add 57 | additional color spaces going forward. (MichaelMauderer) 58 | * Added the IPT color space. (MichaelMauderer) 59 | * Added Color Appearance Models. Natayani95, Hunt, RLAB, ATD95, 60 | LLAB, CIECAM02, CIECAM02-m1. (MichaelMauderer) 61 | 62 | Bug Fixes 63 | ^^^^^^^^^ 64 | 65 | * xyY conversions now correctly avoid division by zero. (dwbullok) 66 | * Un-transposed adaptation matrices. Has no effect on conversions, 67 | but if you use these directly you may see different numbers. (JasonTam) 68 | * During XYZ->RGB, linear channel values are now clamped in order to avoid 69 | domain errors. Output should now always be between 0 and 1. 70 | 71 | Backwards Incompatible changes 72 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 73 | 74 | * If any of your code directly referenced the color adaptation matrices 75 | that were un-inverted, you'll need to adjust your math. 76 | 77 | 2.0.2 78 | ----- 79 | 80 | Bug Fixes 81 | ^^^^^^^^^ 82 | 83 | * Apparently I didn't add the function body for the clamped RGB properties. 84 | Yikes. 85 | 86 | 2.0.1 87 | ----- 88 | 89 | Features 90 | ^^^^^^^^ 91 | * Lots of documentation improvements. 92 | * :py:meth:`convert_color()` now has an explicitly defined/documented 93 | ``target_illuminant`` kwarg, instead of letting this fall through to its 94 | **kwargs. This should make IDE auto-complete better and provide more clarity. 95 | * Added ``clamped_rgb_r``, ``clamped_rgb_g``, and ``clamped_rgb_b`` to RGB 96 | color spaces. Use these if you have to have in-gamut, potentially compressed 97 | coordinates. 98 | 99 | Bug Fixes 100 | ^^^^^^^^^ 101 | * Direct conversions to non-sRGB colorspaces returned sRGBColor objects. 102 | Reported by Cezary Wagner. 103 | 104 | 2.0.0 105 | ----- 106 | 107 | Backwards Incompatible changes 108 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 109 | * Minimum Python version is now 2.7. 110 | * ColorBase.convert_to() is no more. Use colormath.color_conversions.convert_color() 111 | instead. API isn't as spiffy looking, but it's a lot less magical now. 112 | * Completely re-worked RGB handling. Each RGB space now has its own class, 113 | inheriting from BaseRGBColor. Consequently, RGBColor is no more. In most 114 | cases, you can substitute RGBColor with sRGBColor during your upgrade. 115 | * RGB channels are now [0..1] instead of [1..255]. Can use 116 | BaseRGBColor.get_upscaled_value_tuple() to get the upscaled values. 117 | * BaseRGBColor.set_from_rgb_hex() was replaced with a 118 | BaseRGBColor.new_from_rgb_hex(), which returns a properly formed sRGBColor object. 119 | * BaseRGBColor no longer accepts observer or illuminant kwargs. 120 | * HSL no longer accepts observer, illuminant or rgb_type kwargs. 121 | * HSV no longer accepts observer, illuminant or rgb_type kwargs. 122 | * CMY no longer accepts observer, illuminant or rgb_type kwargs. 123 | * CMYK no longer accepts observer, illuminant or rgb_type kwargs. 124 | * Removed 'debug' kwargs in favor of Python's logging. 125 | * Completely re-worked exception list. Eliminated some redundant exceptions, 126 | re-named basically everything else. 127 | 128 | Features 129 | ^^^^^^^^ 130 | * Python 3.3 support added. 131 | * Added tox.ini with supported environments. 132 | * Removed the old custom test runner in favor of nose. 133 | * Replacing simplified RGB->XYZ conversions with Bruce Lindbloom's. 134 | * A round of PEP8 work. 135 | * Added a BaseColorConversionTest test class with some greatly improved 136 | color comparison. Much more useful in tracking down breakages. 137 | * Eliminated non-matrix delta E computations in favor of the matrix equivalents. 138 | No need to maintain duplicate code, and the matrix stuff is faster for bulk 139 | operations. 140 | 141 | Bug Fixes 142 | ^^^^^^^^^ 143 | 144 | * Corrected delta_e CMC example error. Should now run correctly. 145 | * color_diff_matrix.delta_e_cie2000 had an edge case where certain angles 146 | would result in an incorrect delta E. 147 | * Un-hardcoded XYZColor.apply_adaptation()'s adaptation and observer angles. 148 | 149 | 1.0.9 150 | ----- 151 | 152 | Features 153 | ^^^^^^^^ 154 | * Added an optional vectorized deltaE function. This uses NumPy array/matrix 155 | math for a very large speed boost. (Eddie Bell) 156 | * Added this changelog. 157 | 158 | Bug Fixes 159 | ^^^^^^^^^ 160 | * Un-hardcode the observer angle in adaptation matrix. (Bastien Dejean) 161 | 162 | 1.0.8 163 | ----- 164 | 165 | * Initial GitHub release. 166 | 167 | -------------------------------------------------------------------------------- /examples/color_appearance_model.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from colormath.color_appearance_models import CIECAM02 3 | from colormath.color_objects import XYZColor 4 | 5 | 6 | # Color stimulus 7 | color = XYZColor(19.01, 20, 21.78) 8 | 9 | # Illuminant 10 | illuminant_d65 = XYZColor(95.05, 100, 108.88) 11 | 12 | # Background relative luminance 13 | y_b_dark = 10 14 | y_b_light = 100 15 | 16 | # Adapting luminance 17 | l_a = 328.31 18 | 19 | # Surround condition assumed to be average (see CIECAM02 documentation for values) 20 | c = 0.69 21 | n_c = 1 22 | f = 1 23 | 24 | model_a = CIECAM02( 25 | color.xyz_x, 26 | color.xyz_y, 27 | color.xyz_z, 28 | illuminant_d65.xyz_x, 29 | illuminant_d65.xyz_y, 30 | illuminant_d65.xyz_z, 31 | y_b_dark, 32 | l_a, 33 | c, 34 | n_c, 35 | f, 36 | ) 37 | model_b = CIECAM02( 38 | color.xyz_x, 39 | color.xyz_y, 40 | color.xyz_z, 41 | illuminant_d65.xyz_x, 42 | illuminant_d65.xyz_y, 43 | illuminant_d65.xyz_z, 44 | y_b_light, 45 | l_a, 46 | c, 47 | n_c, 48 | f, 49 | ) 50 | 51 | print("== CIECAM02 Predictions ==") 52 | 53 | print("Observed under CIE illuminant D65") 54 | print( 55 | "Lightness {:.2f}, saturation {:.2f}, hue {:.2f}".format( 56 | model_a.lightness, model_a.saturation, model_a.hue_angle 57 | ) 58 | ) 59 | print("Observed under CIE illuminant A") 60 | print( 61 | "Lightness {:.2f}, saturation {:.2f}, hue {:.2f}".format( 62 | model_b.lightness, model_b.saturation, model_b.hue_angle 63 | ) 64 | ) 65 | -------------------------------------------------------------------------------- /examples/conversions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | This module shows you how to perform color space conversions. Please see the 4 | chart on www.brucelindbloom.com/Math.html for an illustration of the conversions 5 | you may perform. 6 | """ 7 | 8 | # Does some sys.path manipulation so we can run examples in-place. 9 | # noinspection PyUnresolvedReferences 10 | import example_config # noqa 11 | 12 | from colormath.color_conversions import convert_color 13 | from colormath.color_objects import ( 14 | LabColor, 15 | LCHabColor, 16 | SpectralColor, 17 | sRGBColor, 18 | XYZColor, 19 | LCHuvColor, 20 | IPTColor, 21 | ) 22 | 23 | 24 | def example_lab_to_xyz(): 25 | """ 26 | This function shows a simple conversion of an Lab color to an XYZ color. 27 | """ 28 | 29 | print("=== Simple Example: Lab->XYZ ===") 30 | # Instantiate an Lab color object with the given values. 31 | lab = LabColor(0.903, 16.296, -2.22) 32 | # Show a string representation. 33 | print(lab) 34 | # Convert to XYZ. 35 | xyz = convert_color(lab, XYZColor) 36 | print(xyz) 37 | print("=== End Example ===\n") 38 | 39 | 40 | def example_lchab_to_lchuv(): 41 | """ 42 | This function shows very complex chain of conversions in action. 43 | 44 | LCHab to LCHuv involves four different calculations, making this the 45 | conversion requiring the most steps. 46 | """ 47 | 48 | print("=== Complex Example: LCHab->LCHuv ===") 49 | # Instantiate an LCHab color object with the given values. 50 | lchab = LCHabColor(0.903, 16.447, 352.252) 51 | # Show a string representation. 52 | print(lchab) 53 | # Convert to LCHuv. 54 | lchuv = convert_color(lchab, LCHuvColor) 55 | print(lchuv) 56 | print("=== End Example ===\n") 57 | 58 | 59 | def example_lab_to_rgb(): 60 | """ 61 | Conversions to RGB are a little more complex mathematically. There are also 62 | several kinds of RGB color spaces. When converting from a device-independent 63 | color space to RGB, sRGB is assumed unless otherwise specified with the 64 | target_rgb keyword arg. 65 | """ 66 | 67 | print("=== RGB Example: Lab->RGB ===") 68 | # Instantiate an Lab color object with the given values. 69 | lab = LabColor(0.903, 16.296, -2.217) 70 | # Show a string representation. 71 | print(lab) 72 | # Convert to XYZ. 73 | rgb = convert_color(lab, sRGBColor) 74 | print(rgb) 75 | print("=== End Example ===\n") 76 | 77 | 78 | def example_rgb_to_xyz(): 79 | """ 80 | The reverse is similar. 81 | """ 82 | 83 | print("=== RGB Example: RGB->XYZ ===") 84 | # Instantiate an Lab color object with the given values. 85 | rgb = sRGBColor(120, 130, 140) 86 | # Show a string representation. 87 | print(rgb) 88 | # Convert RGB to XYZ using a D50 illuminant. 89 | xyz = convert_color(rgb, XYZColor, target_illuminant="D50") 90 | print(xyz) 91 | print("=== End Example ===\n") 92 | 93 | 94 | def example_spectral_to_xyz(): 95 | """ 96 | Instantiate an Lab color object with the given values. Note that the 97 | spectral range can run from 340nm to 830nm. Any omitted values assume a 98 | value of 0.0, which is more or less ignored. For the distribution below, 99 | we are providing an example reading from an X-Rite i1 Pro, which only 100 | measures between 380nm and 730nm. 101 | """ 102 | 103 | print("=== Example: Spectral->XYZ ===") 104 | spc = SpectralColor( 105 | observer="2", 106 | illuminant="d50", 107 | spec_380nm=0.0600, 108 | spec_390nm=0.0600, 109 | spec_400nm=0.0641, 110 | spec_410nm=0.0654, 111 | spec_420nm=0.0645, 112 | spec_430nm=0.0605, 113 | spec_440nm=0.0562, 114 | spec_450nm=0.0543, 115 | spec_460nm=0.0537, 116 | spec_470nm=0.0541, 117 | spec_480nm=0.0559, 118 | spec_490nm=0.0603, 119 | spec_500nm=0.0651, 120 | spec_510nm=0.0680, 121 | spec_520nm=0.0705, 122 | spec_530nm=0.0736, 123 | spec_540nm=0.0772, 124 | spec_550nm=0.0809, 125 | spec_560nm=0.0870, 126 | spec_570nm=0.0990, 127 | spec_580nm=0.1128, 128 | spec_590nm=0.1251, 129 | spec_600nm=0.1360, 130 | spec_610nm=0.1439, 131 | spec_620nm=0.1511, 132 | spec_630nm=0.1590, 133 | spec_640nm=0.1688, 134 | spec_650nm=0.1828, 135 | spec_660nm=0.1996, 136 | spec_670nm=0.2187, 137 | spec_680nm=0.2397, 138 | spec_690nm=0.2618, 139 | spec_700nm=0.2852, 140 | spec_710nm=0.2500, 141 | spec_720nm=0.2400, 142 | spec_730nm=0.2300, 143 | ) 144 | xyz = convert_color(spc, XYZColor) 145 | print(xyz) 146 | print("=== End Example ===\n") 147 | 148 | 149 | def example_lab_to_ipt(): 150 | """ 151 | This function shows a simple conversion of an XYZ color to an IPT color. 152 | """ 153 | 154 | print("=== Simple Example: XYZ->IPT ===") 155 | # Instantiate an XYZ color object with the given values. 156 | xyz = XYZColor(0.5, 0.5, 0.5, illuminant="d65") 157 | # Show a string representation. 158 | print(xyz) 159 | # Convert to IPT. 160 | ipt = convert_color(xyz, IPTColor) 161 | print(ipt) 162 | print("=== End Example ===\n") 163 | 164 | 165 | # Feel free to comment/un-comment examples as you please. 166 | example_lab_to_xyz() 167 | example_lchab_to_lchuv() 168 | example_lab_to_rgb() 169 | example_spectral_to_xyz() 170 | example_rgb_to_xyz() 171 | example_lab_to_ipt() 172 | -------------------------------------------------------------------------------- /examples/delta_e.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | This module shows some examples of Delta E calculations of varying types. 4 | """ 5 | 6 | # Does some sys.path manipulation so we can run examples in-place. 7 | # noinspection PyUnresolvedReferences 8 | import example_config # noqa 9 | 10 | from colormath.color_objects import LabColor 11 | from colormath.color_diff import ( 12 | delta_e_cie1976, 13 | delta_e_cie1994, 14 | delta_e_cie2000, 15 | delta_e_cmc, 16 | ) 17 | 18 | # Reference color. 19 | color1 = LabColor(lab_l=0.9, lab_a=16.3, lab_b=-2.22) 20 | # Color to be compared to the reference. 21 | color2 = LabColor(lab_l=0.7, lab_a=14.2, lab_b=-1.80) 22 | 23 | print("== Delta E Colors ==") 24 | print(" COLOR1: %s" % color1) 25 | print(" COLOR2: %s" % color2) 26 | print("== Results ==") 27 | print(" CIE1976: %.3f" % delta_e_cie1976(color1, color2)) 28 | print(" CIE1994: %.3f (Graphic Arts)" % delta_e_cie1994(color1, color2)) 29 | # Different values for textiles. 30 | print( 31 | " CIE1994: %.3f (Textiles)" 32 | % delta_e_cie1994(color1, color2, K_1=0.048, K_2=0.014, K_L=2) 33 | ) 34 | print(" CIE2000: %.3f" % delta_e_cie2000(color1, color2)) 35 | # Typically used for acceptability. 36 | print(" CMC: %.3f (2:1)" % delta_e_cmc(color1, color2, pl=2, pc=1)) 37 | # Typically used to more closely model human perception. 38 | print(" CMC: %.3f (1:1)" % delta_e_cmc(color1, color2, pl=1, pc=1)) 39 | -------------------------------------------------------------------------------- /examples/delta_e_matrix.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | For a massive matrix of colors and color labels you can download 4 | the follow two files 5 | 6 | # http://lyst-classifiers.s3.amazonaws.com/color/lab-colors.pk 7 | # http://lyst-classifiers.s3.amazonaws.com/color/lab-matrix.pk 8 | 9 | lab-colors is a cPickled list of color names and lab-matrix is a 10 | cPickled (n,3) numpy array LAB values such that row q maps to 11 | index q in the lab color list 12 | """ 13 | 14 | import sys 15 | import csv 16 | import bz2 17 | 18 | import numpy as np 19 | 20 | # Does some sys.path manipulation so we can run examples in-place. 21 | # noinspection PyUnresolvedReferences 22 | import example_config # noqa 23 | 24 | from colormath.color_diff_matrix import delta_e_cie2000 25 | from colormath.color_objects import LabColor 26 | 27 | 28 | # load list of 1000 random colors from the XKCD color chart 29 | if sys.version_info >= (3, 0): 30 | reader = csv.DictReader(bz2.open("lab_matrix.csv.bz2", mode="rt")) 31 | lab_matrix = np.array([list(map(float, row.values())) for row in reader]) 32 | else: 33 | reader = csv.DictReader(bz2.BZ2File("lab_matrix.csv.bz2")) 34 | lab_matrix = np.array([map(float, row.values()) for row in reader]) 35 | 36 | color = LabColor(lab_l=69.34, lab_a=-0.88, lab_b=-52.57) 37 | lab_color_vector = np.array([color.lab_l, color.lab_a, color.lab_b]) 38 | delta = delta_e_cie2000(lab_color_vector, lab_matrix) 39 | 40 | print("%s is closest to %s" % (color, lab_matrix[np.argmin(delta)])) 41 | -------------------------------------------------------------------------------- /examples/density.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | This module shows you how to perform various kinds of density calculations. 4 | """ 5 | 6 | # Does some sys.path manipulation so we can run examples in-place. 7 | # noinspection PyUnresolvedReferences 8 | import example_config # noqa 9 | 10 | from colormath.color_objects import SpectralColor 11 | from colormath.density_standards import ANSI_STATUS_T_RED, ISO_VISUAL 12 | 13 | EXAMPLE_COLOR = SpectralColor( 14 | observer=2, 15 | illuminant="d50", 16 | spec_380nm=0.0600, 17 | spec_390nm=0.0600, 18 | spec_400nm=0.0641, 19 | spec_410nm=0.0654, 20 | spec_420nm=0.0645, 21 | spec_430nm=0.0605, 22 | spec_440nm=0.0562, 23 | spec_450nm=0.0543, 24 | spec_460nm=0.0537, 25 | spec_470nm=0.0541, 26 | spec_480nm=0.0559, 27 | spec_490nm=0.0603, 28 | spec_500nm=0.0651, 29 | spec_510nm=0.0680, 30 | spec_520nm=0.0705, 31 | spec_530nm=0.0736, 32 | spec_540nm=0.0772, 33 | spec_550nm=0.0809, 34 | spec_560nm=0.0870, 35 | spec_570nm=0.0990, 36 | spec_580nm=0.1128, 37 | spec_590nm=0.1251, 38 | spec_600nm=0.1360, 39 | spec_610nm=0.1439, 40 | spec_620nm=0.1511, 41 | spec_630nm=0.1590, 42 | spec_640nm=0.1688, 43 | spec_650nm=0.1828, 44 | spec_660nm=0.1996, 45 | spec_670nm=0.2187, 46 | spec_680nm=0.2397, 47 | spec_690nm=0.2618, 48 | spec_700nm=0.2852, 49 | spec_710nm=0.2500, 50 | spec_720nm=0.2400, 51 | spec_730nm=0.2300, 52 | ) 53 | 54 | 55 | def example_auto_status_t_density(): 56 | print("=== Example: Automatic Status T Density ===") 57 | # If no arguments are provided to calc_density(), ANSI Status T density is 58 | # assumed. The correct RGB "filter" is automatically selected for you. 59 | print("Density: %f" % EXAMPLE_COLOR.calc_density()) 60 | print("=== End Example ===\n") 61 | 62 | 63 | def example_manual_status_t_density(): 64 | print("=== Example: Manual Status T Density ===") 65 | # Here we are specifically requesting the value of the red band via the 66 | # ANSI Status T spec. 67 | print( 68 | "Density: %f (Red)" 69 | % EXAMPLE_COLOR.calc_density(density_standard=ANSI_STATUS_T_RED) 70 | ) 71 | print("=== End Example ===\n") 72 | 73 | 74 | def example_visual_density(): 75 | print("=== Example: Visual Density ===") 76 | # Here we pass the ISO Visual spectral standard. 77 | print("Density: %f" % EXAMPLE_COLOR.calc_density(density_standard=ISO_VISUAL)) 78 | print("=== End Example ===\n") 79 | 80 | 81 | # Feel free to comment/un-comment examples as you please. 82 | example_auto_status_t_density() 83 | example_manual_status_t_density() 84 | example_visual_density() 85 | -------------------------------------------------------------------------------- /examples/example_config.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | This file holds various configuration options used for all of the examples. 4 | """ 5 | import os 6 | import sys 7 | 8 | # Use the colormath directory included in the downloaded package instead of 9 | # any globally installed versions. 10 | sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 11 | -------------------------------------------------------------------------------- /examples/lab_matrix.csv.bz2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gtaylor/python-colormath/6d3261f9c8a2449bdffdf563eb541c8b6846af2d/examples/lab_matrix.csv.bz2 -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | networkx>=2.0 2 | numpy 3 | nose 4 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import colormath 4 | 5 | from setuptools import setup 6 | 7 | LONG_DESCRIPTION = open("README.rst").read() 8 | 9 | CLASSIFIERS = [ 10 | "Development Status :: 5 - Production/Stable", 11 | "Intended Audience :: Developers", 12 | "License :: OSI Approved :: BSD License", 13 | "Natural Language :: English", 14 | "Operating System :: OS Independent", 15 | "Programming Language :: Python", 16 | "Topic :: Scientific/Engineering :: Mathematics", 17 | "Topic :: Software Development :: Libraries :: Python Modules", 18 | "Programming Language :: Python :: 2.7", 19 | "Programming Language :: Python :: 3.5", 20 | "Programming Language :: Python :: 3.6", 21 | "Programming Language :: Python :: 3.7", 22 | "Programming Language :: Python :: 3.8", 23 | ] 24 | 25 | KEYWORDS = "color math conversions" 26 | 27 | setup( 28 | name="colormath", 29 | version=colormath.VERSION, 30 | description="Color math and conversion library.", 31 | long_description=LONG_DESCRIPTION, 32 | author="Gregory Taylor", 33 | author_email="gtaylor@gc-taylor.com", 34 | url="https://github.com/gtaylor/python-colormath", 35 | download_url="http://pypi.python.org/pypi/colormath/", 36 | packages=["colormath"], 37 | platforms=["Platform Independent"], 38 | license="BSD", 39 | classifiers=CLASSIFIERS, 40 | keywords=KEYWORDS, 41 | install_requires=["numpy", "networkx>=2.0"], 42 | extras_require={"development": ["black", "flake8", "nose", "pre-commit", "sphinx"]}, 43 | ) 44 | -------------------------------------------------------------------------------- /tests/fixtures/atd.csv: -------------------------------------------------------------------------------- 1 | Case,X,Y,Z,X_0,Y_0,Z_0,Y_02,sigma,K_1,K_2,A_1,T_1,D_1,A_2,T_2,D_2,Br,C,H 2 | 1,19.01,20,21.78,95.05,100,108.88,318.31,300,0,50,0.1788,0.0287,0.0108,0.0192,0.0205,0.0108,0.1814,1.206,1.91 3 | 2,57.06,43.06,31.96,95.05,100,108.88,31.83,300,0,50,0.2031,0.068,0.0005,0.0224,0.0308,0.0005,0.2142,1.371,63.96 4 | 3,3.53,6.56,2.14,109.85,100,35.58,318.31,300,0,50,0.1068,-0.011,0.0044,0.0106,-0.0014,0.0044,0.1075,0.436,-0.31 5 | 4,19.01,20,21.78,109.85,100,35.58,31.83,300,0,50,0.146,0.0007,0.013,0.0152,0.0102,0.013,0.1466,1.091,0.79 6 | -------------------------------------------------------------------------------- /tests/fixtures/ciecam02.csv: -------------------------------------------------------------------------------- 1 | Case,X,Y,Z,X_W,Y_W,Z_W,L_A,F,D,Y_b,N_c,F_L,N_bb,N_cb,H,H,H_C,J,Q,S,C,M,a_c,b_c,a_M,b_M,a_s,b_s,c 2 | 1,19.01,20,21.78,95.05,100,108.88,318.31,1,0.994,20,1,1.17,1,1,219,278.1,78B 22G,41.73,195.37,2.36,0.1,0.11,-0.08,-0.07,-0.08,-0.07,-1.83,-1.49,0.69 3 | 2,57.06,43.06,31.96,95.05,100,108.88,31.83,1,0.875,20,1,0.54,1,1,19.6,399.6,100R,65.96,152.67,52.25,48.57,41.67,45.77,16.26,39.27,13.95,49.23,17.49,0.69 4 | 3,3.53,6.56,2.14,109.85,100,35.58,318.31,1,0.994,20,1,1.17,1,1,177.1,220.4,80G 20B,21.79,141.17,58.79,46.94,48.8,-46.89,2.34,-48.74,2.43,-58.72,2.93,0.69 5 | 4,19.01,20,21.78,109.85,100,35.58,31.83,1,0.875,20,1,0.54,1,1,248.9,305.8,94B6R,42.53,122.83,60.22,51.92,44.54,-18.69,-48.44,-16.03,-41.56,-21.67,-56.18,0.69 6 | -------------------------------------------------------------------------------- /tests/fixtures/hunt.csv: -------------------------------------------------------------------------------- 1 | Case,X,Y,Z,X_W,Y_W,Z_W,L_A,N_c,N_b,Discounting,h_S,H,s,Q,J,C_94,M94,T 2 | 1,19.01,20,21.78,95.05,100,108.88,318.31,1,75,1,269.3,317.2,0.03,31.92,42.12,0.16,0.16,6504 3 | 2,57.06,43.06,31.96,95.05,100,108.88,31.83,1,75,1,18.6,398.8,153.36,31.22,66.76,63.89,58.28,6504 4 | 3,3.53,6.56,2.14,109.85,100,35.58,318.31,1,75,1,178.3,222.2,245.4,18.9,19.56,74.58,76.33,2856 5 | 4,19.01,20,21.78,109.84,100,35.58,31.83,1,75,1,262.8,313.4,209.29,22.15,40.27,73.84,67.35,2856 6 | -------------------------------------------------------------------------------- /tests/fixtures/llab.csv: -------------------------------------------------------------------------------- 1 | Case,X,Y,Z,X_0,Y_0,Z_0,L,Y_b,F_S,F_L,F_C,L_L,Ch_L,C_L,s_L,h_L,A_L,B_L 2 | 1,19.01,20,21.78,95.05,100,108.88,318.31,20,3,1,1,37.37,0.01,0.02,0,229.5,-0.01,-0.01 3 | 2,57.06,43.06,31.96,95.05,100,108.88,31.83,20,3,1,1,61.26,30.51,56.55,0.5,22.3,52.33,21.43 4 | 3,3.53,6.56,2.14,109.85,100,35.58,318.31,20,3,1,1,16.25,30.43,53.83,1.87,173.8,-53.51,5.83 5 | 4,19.01,20,21.78,109.85,100,35.58,31.83,20,3,1,1,39.82,29.34,54.59,0.74,271.9,1.76,-54.56 6 | -------------------------------------------------------------------------------- /tests/fixtures/nayatani.csv: -------------------------------------------------------------------------------- 1 | Case,X,Y,Z,X_n,Y_n,Z_n,E_o,E_or,B_r,L_star_P,L_star_N,theta,H,S,C,M,Y_o 2 | 1,19.01,20,21.78,95.05,100,108.88,5000,1000,62.6,50,50,257.5,317.8,0.01,0.01,0.02,20 3 | 2,57.06,43.06,31.96,95.05,100,108.88,500,1000,67.3,73,75.9,21.6,2.1,37.1,48.3,42.9,20 4 | 3,3.53,6.56,2.14,109.85,100,35.58,5000,1000,37.5,24.5,29.7,190.6,239.4,81.3,49.3,62.1,20 5 | 4,19.01,20,21.78,109.85,100,35.58,500,1000,44.2,49.4,49.4,236.3,303.6,40.2,39.9,35.8,20 6 | -------------------------------------------------------------------------------- /tests/fixtures/rlab.csv: -------------------------------------------------------------------------------- 1 | Case,X,Y,Z,X_n,Y_n,Z_n,Y_n2,sigma,D,L,a,b,h,C,s 2 | 1,19.01,20,21.78,95.05,100,108.88,318.31,0.4347,1,49.67,0,-0.01,270,0.01,0 3 | 2,57.06,43.06,31.96,95.05,100,108.88,31.83,0.4347,1,69.33,46.33,18.09,21.3,49.74,0.72 4 | 3,3.53,6.56,2.14,109.85,100,35.58,318.31,0.4347,1,30.78,-40.96,2.25,176.9,41.02,1.33 5 | 4,19.01,20,21.78,109.85,100,35.58,31.83,0.4347,1,49.83,15.57,-52.61,286.5,54.87,1.1 6 | -------------------------------------------------------------------------------- /tests/test_chromatic_adaptation.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Tests for color difference (Delta E) equations. 4 | """ 5 | 6 | import unittest 7 | 8 | from colormath.color_objects import XYZColor 9 | 10 | 11 | # noinspection PyPep8Naming 12 | class chromaticAdaptationTestCase(unittest.TestCase): 13 | def setUp(self): 14 | self.color = XYZColor(0.5, 0.4, 0.1, illuminant="C") 15 | 16 | def test_adaptation_c_to_d65(self): 17 | self.color.apply_adaptation(target_illuminant="D65") 18 | self.assertAlmostEqual( 19 | self.color.xyz_x, 0.491, 3, "C to D65 adaptation failed: X coord" 20 | ) 21 | self.assertAlmostEqual( 22 | self.color.xyz_y, 0.400, 3, "C to D65 adaptation failed: Y coord" 23 | ) 24 | self.assertAlmostEqual( 25 | self.color.xyz_z, 0.093, 3, "C to D65 adaptation failed: Z coord" 26 | ) 27 | self.assertEqual( 28 | self.color.illuminant, 29 | "d65", 30 | "C to D65 adaptation failed: Illuminant transfer", 31 | ) 32 | -------------------------------------------------------------------------------- /tests/test_color_appearance_models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Copyright (c) 2014, Michael Mauderer, University of St Andrews 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without modification, 7 | are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | * Neither the name of the University of St Andrews nor the names of its 15 | contributors may be used to endorse or promote products derived from this 16 | software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | """ 29 | 30 | from abc import abstractmethod 31 | from collections import defaultdict 32 | import csv 33 | import os 34 | 35 | import unittest 36 | import numpy 37 | 38 | from numpy.testing import assert_allclose, assert_almost_equal 39 | from colormath.color_appearance_models import ( 40 | CIECAM02, 41 | RLAB, 42 | LLAB, 43 | ATD95, 44 | Nayatani95, 45 | Hunt, 46 | ) 47 | 48 | 49 | class ColorAppearanceTest(object): 50 | fixture_path = None 51 | output_parameter_dict = {} 52 | 53 | @staticmethod 54 | def load_fixture(file_name): 55 | path = os.path.dirname(__file__) 56 | with open(os.path.join(path, "fixtures", file_name)) as in_file: 57 | result = [] 58 | for case_data in csv.DictReader(in_file): 59 | for key in case_data: 60 | try: 61 | case_data[key] = float(case_data[key]) 62 | except ValueError: 63 | pass 64 | result.append(case_data) 65 | return result 66 | 67 | def check_model_consistency(self, data, output_parameter_dict): 68 | for data_attr, model_attr in sorted(output_parameter_dict.items()): 69 | yield self.check_model_attribute, data.get("Case"), data, model_attr, data[ 70 | data_attr 71 | ] 72 | 73 | @abstractmethod 74 | def create_model_from_data(self, data): 75 | pass 76 | 77 | def check_model_attribute(self, case, data, model_attr, target): 78 | model = self.create_model_from_data(data) 79 | model_parameter = getattr(model, model_attr) 80 | error_message = ( 81 | "Parameter {} in test case {} does not match target value." 82 | "\nExpected: {} \nReceived {}" 83 | ).format(model_attr, case, target, model_parameter) 84 | 85 | assert_allclose( 86 | model_parameter, 87 | target, 88 | err_msg=error_message, 89 | rtol=0.01, 90 | atol=0.01, 91 | verbose=False, 92 | ) 93 | assert_almost_equal(model_parameter, target, decimal=1, err_msg=error_message) 94 | 95 | limited_fixtures = None 96 | 97 | def _get_fixtures(self): 98 | # Sometimes it might be desirable to exclude s specific fixture for testing 99 | fixtures = self.load_fixture(self.fixture_path) 100 | if self.limited_fixtures is not None: 101 | fixtures = [fixtures[index] for index in self.limited_fixtures] 102 | return fixtures 103 | 104 | def test_forward_examples(self): 105 | # Go through all available fixtures 106 | for data in self._get_fixtures(): 107 | # Create a single test for each output parameter 108 | for test in self.check_model_consistency(data, self.output_parameter_dict): 109 | yield test 110 | 111 | def test_parallel_forward_example(self): 112 | # Collect all fixture data in a single dict of lists 113 | data = defaultdict(list) 114 | for fixture in self._get_fixtures(): 115 | for key, value in fixture.items(): 116 | data[key].append(value) 117 | # Turn lists into numpy.arrays 118 | for key in data: 119 | data[key] = numpy.array(data[key]) 120 | # Create tests 121 | for test in self.check_model_consistency(data, self.output_parameter_dict): 122 | yield test 123 | 124 | 125 | class TestNayataniColorAppearanceModel(ColorAppearanceTest): 126 | fixture_path = "nayatani.csv" 127 | 128 | output_parameter_dict = { 129 | "L_star_P": "_lightness_achromatic", 130 | "L_star_N": "_lightness_achromatic_normalized", 131 | "theta": "hue_angle", 132 | "C": "chroma", 133 | "S": "saturation", 134 | "B_r": "brightness", 135 | "M": "colorfulness", 136 | } 137 | 138 | def create_model_from_data(self, data): 139 | model = Nayatani95( 140 | data["X"], 141 | data["Y"], 142 | data["Z"], 143 | data["X_n"], 144 | data["Y_n"], 145 | data["Z_n"], 146 | data["Y_o"], 147 | data["E_o"], 148 | data["E_or"], 149 | ) 150 | return model 151 | 152 | @staticmethod 153 | def test_beta_1(): 154 | assert_almost_equal(Nayatani95._beta_1(0), 1, decimal=6) 155 | assert_almost_equal(Nayatani95._beta_1(1), 1.717900656, decimal=6) 156 | assert_almost_equal(Nayatani95._beta_1(2), 1.934597896, decimal=6) 157 | 158 | @staticmethod 159 | def test_beta_2(): 160 | assert_almost_equal(Nayatani95._beta_2(0), 0.7844, decimal=6) 161 | assert_almost_equal(Nayatani95._beta_2(1), 1.375241343, decimal=6) 162 | assert_almost_equal(Nayatani95._beta_2(2), 1.59085866, decimal=6) 163 | 164 | 165 | class TestHuntColorAppearanceModel(ColorAppearanceTest): 166 | fixture_path = "hunt.csv" 167 | 168 | output_parameter_dict = { 169 | "h_S": "hue_angle", 170 | "s": "saturation", 171 | "Q": "brightness", 172 | "J": "lightness", 173 | "C_94": "chroma", 174 | "M94": "colorfulness", 175 | } 176 | 177 | def create_model_from_data(self, data): 178 | return Hunt( 179 | data["X"], 180 | data["Y"], 181 | data["Z"], 182 | data["X_W"], 183 | 0.2 * data["Y_W"], 184 | data["Z_W"], 185 | data["X_W"], 186 | data["Y_W"], 187 | data["Z_W"], 188 | l_a=data["L_A"], 189 | n_c=data["N_c"], 190 | n_b=data["N_b"], 191 | cct_w=data["T"], 192 | ) 193 | 194 | 195 | class TestRLABColorAppearanceModel(ColorAppearanceTest): 196 | fixture_path = "rlab.csv" 197 | output_parameter_dict = { 198 | "L": "lightness", 199 | "C": "chroma", 200 | "s": "saturation", 201 | "a": "a", 202 | "b": "b", 203 | "h": "hue_angle", 204 | } 205 | 206 | def create_model_from_data(self, data): 207 | model = RLAB( 208 | data["X"], 209 | data["Y"], 210 | data["Z"], 211 | data["X_n"], 212 | data["Y_n"], 213 | data["Z_n"], 214 | data["Y_n2"], 215 | data["sigma"], 216 | data["D"], 217 | ) 218 | return model 219 | 220 | 221 | class TestATDColorAppearanceModel(ColorAppearanceTest): 222 | fixture_path = "atd.csv" 223 | 224 | output_parameter_dict = { 225 | "A_1": "_a_1", 226 | "T_1": "_t_1", 227 | "D_1": "_d_1", 228 | "A_2": "_a_2", 229 | "T_2": "_t_2", 230 | "D_2": "_d_2", 231 | "Br": "brightness", 232 | "C": "saturation", 233 | "H": "hue", 234 | } 235 | 236 | def create_model_from_data(self, data): 237 | model = ATD95( 238 | data["X"], 239 | data["Y"], 240 | data["Z"], 241 | data["X_0"], 242 | data["Y_0"], 243 | data["Z_0"], 244 | data["Y_02"], 245 | data["K_1"], 246 | data["K_2"], 247 | data["sigma"], 248 | ) 249 | return model 250 | 251 | @staticmethod 252 | def test_xyz_to_lms(): 253 | l, m, s = ATD95._xyz_to_lms(numpy.array([1, 1, 1])) 254 | assert_almost_equal(l, 0.7946522478109985) 255 | assert_almost_equal(m, 0.9303058494144267) 256 | assert_almost_equal(s, 0.7252006614718631) 257 | 258 | @staticmethod 259 | def test_final_response_calculation(): 260 | assert_almost_equal(ATD95._calculate_final_response(0), 0) 261 | assert_almost_equal(ATD95._calculate_final_response(100), 1.0 / 3.0) 262 | assert_almost_equal(ATD95._calculate_final_response(200), 0.5) 263 | assert_almost_equal(ATD95._calculate_final_response(10000), 0.980392157) 264 | 265 | 266 | class TestLLABColorAppearanceModel(ColorAppearanceTest): 267 | fixture_path = "llab.csv" 268 | 269 | output_parameter_dict = { 270 | "L_L": "lightness", 271 | "Ch_L": "chroma", 272 | "s_L": "saturation", 273 | "h_L": "hue_angle", 274 | "A_L": "a_l", 275 | "B_L": "b_l", 276 | } 277 | 278 | def create_model_from_data(self, data): 279 | return LLAB( 280 | data["X"], 281 | data["Y"], 282 | data["Z"], 283 | data["X_0"], 284 | data["Y_0"], 285 | data["Z_0"], 286 | data["Y_b"], 287 | data["F_S"], 288 | data["F_L"], 289 | data["F_C"], 290 | data["L"], 291 | ) 292 | 293 | 294 | class TestCIECAM02ColorAppearanceModel(ColorAppearanceTest): 295 | fixture_path = "ciecam02.csv" 296 | output_parameter_dict = { 297 | "J": "lightness", 298 | "Q": "brightness", 299 | "C": "chroma", 300 | "M": "colorfulness", 301 | "S": "saturation", 302 | "N_bb": "n_bb", 303 | "a_c": "a_c", 304 | "b_c": "b_c", 305 | "a_M": "a_m", 306 | "b_M": "b_m", 307 | "a_s": "a_s", 308 | "b_s": "b_s", 309 | } 310 | 311 | input_parameter_dict = {} 312 | 313 | def create_model_from_data(self, data): 314 | return CIECAM02( 315 | data["X"], 316 | data["Y"], 317 | data["Z"], 318 | data["X_W"], 319 | data["Y_W"], 320 | data["Z_W"], 321 | data["Y_b"], 322 | data["L_A"], 323 | data["c"], 324 | data["N_c"], 325 | data["F"], 326 | ) 327 | 328 | @staticmethod 329 | def test_degree_of_adaptation(): 330 | assert_almost_equal( 331 | CIECAM02._compute_degree_of_adaptation(1, 100), 0.940656, decimal=6 332 | ) 333 | assert_almost_equal( 334 | CIECAM02._compute_degree_of_adaptation(1, 318.31), 0.9994, decimal=2 335 | ) 336 | assert_almost_equal( 337 | CIECAM02._compute_degree_of_adaptation(1, 31.83), 0.875, decimal=2 338 | ) 339 | 340 | @staticmethod 341 | def test_xyz_to_rgb(): 342 | assert_almost_equal( 343 | CIECAM02._xyz_to_rgb(numpy.array([95.05, 100, 108.9])), 344 | numpy.array([94.9273, 103.527, 108.737]), 345 | decimal=2, 346 | ) 347 | 348 | 349 | @unittest.skip 350 | class TestCIECAM02m1ColorAppearanceModel(ColorAppearanceTest): 351 | # TODO: Test CIECAM02-m1 model 352 | pass 353 | -------------------------------------------------------------------------------- /tests/test_color_conversion.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import itertools 3 | import numpy as np 4 | import unittest 5 | from colormath import color_conversions 6 | from colormath.color_conversions import ( 7 | GraphConversionManager, 8 | XYZ_to_RGB, 9 | HSV_to_RGB, 10 | RGB_to_XYZ, 11 | ) 12 | from colormath.color_exceptions import UndefinedConversionError 13 | from colormath.color_objects import ( 14 | XYZColor, 15 | BaseRGBColor, 16 | HSVColor, 17 | HSLColor, 18 | AdobeRGBColor, 19 | BT2020Color, 20 | sRGBColor, 21 | ) 22 | 23 | 24 | class GraphConversionManagerTestCase(unittest.TestCase): 25 | def setUp(self): 26 | self.manager = GraphConversionManager() 27 | self.manager.add_type_conversion(XYZColor, BaseRGBColor, XYZ_to_RGB) 28 | self.manager.add_type_conversion(BaseRGBColor, HSVColor, HSV_to_RGB) 29 | 30 | def test_basic_path_generation(self): 31 | path = self.manager.get_conversion_path(XYZColor, HSVColor) 32 | self.assertEqual(path, [XYZ_to_RGB, HSV_to_RGB]) 33 | 34 | def test_self_conversion(self): 35 | path = self.manager.get_conversion_path(XYZColor, XYZColor) 36 | self.assertEqual(path, []) 37 | 38 | def test_invalid_path_response(self): 39 | self.assertRaises( 40 | UndefinedConversionError, 41 | self.manager.get_conversion_path, 42 | XYZColor, 43 | HSLColor, 44 | ) 45 | 46 | 47 | class ColorConversionTestCase(unittest.TestCase): 48 | def test_conversion_validity(self): 49 | """ 50 | Make sure that all existing paths between colors are valid. 51 | """ 52 | 53 | conversion_manager = color_conversions._conversion_manager 54 | color_spaces = conversion_manager.registered_color_spaces 55 | 56 | for start_space, target_space in itertools.product(color_spaces, repeat=2): 57 | try: 58 | path = conversion_manager.get_conversion_path(start_space, target_space) 59 | except UndefinedConversionError: 60 | # If there is no path we don't really care (e.g., conversion to 61 | # SpectralColor). 62 | continue 63 | 64 | if start_space == target_space: 65 | # If start and end color space are equal, the conversion path should 66 | # be empty. 67 | self.assertEqual(path, []) 68 | continue 69 | else: 70 | # Otherwise check that all the conversion functions math up 71 | for a, b in zip(path[:-1], path[1:]): 72 | self.assertEqual(a.target_type, b.start_type) 73 | 74 | def test_transfer_functions(self): 75 | """ 76 | Tests the transfer functions of the various RGB colorspaces. 77 | """ 78 | 79 | for colorspace in (AdobeRGBColor, BT2020Color, sRGBColor): 80 | for a in (0.0, 0.01, 0.18, 1.0): 81 | RGB = [a] * 3 82 | np.testing.assert_allclose( 83 | XYZ_to_RGB(RGB_to_XYZ(colorspace(*RGB)), colorspace).get_value_tuple(), 84 | RGB, 85 | rtol=1e-5, 86 | atol=1e-5, 87 | ) 88 | -------------------------------------------------------------------------------- /tests/test_color_diff.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Tests for color difference (Delta E) equations. 4 | """ 5 | 6 | import unittest 7 | 8 | from colormath.color_diff import ( 9 | delta_e_cie1976, 10 | delta_e_cie1994, 11 | delta_e_cie2000, 12 | delta_e_cmc, 13 | ) 14 | from colormath.color_objects import LabColor, sRGBColor 15 | 16 | 17 | class DeltaETestCase(unittest.TestCase): 18 | def setUp(self): 19 | self.color1 = LabColor(lab_l=0.9, lab_a=16.3, lab_b=-2.22) 20 | self.color2 = LabColor(lab_l=0.7, lab_a=14.2, lab_b=-1.80) 21 | 22 | def test_cie2000_accuracy(self): 23 | result = delta_e_cie2000(self.color1, self.color2) 24 | expected = 1.523 25 | self.assertAlmostEqual( 26 | result, 27 | expected, 28 | 3, 29 | "DeltaE CIE2000 formula error. " 30 | "Got %.3f, expected %.3f (diff: %.3f)." 31 | % (result, expected, result - expected), 32 | ) 33 | 34 | def test_cie2000_accuracy_2(self): 35 | """ 36 | Follow a different execution path based on variable values. 37 | """ 38 | 39 | # These values are from ticket 8 in regards to a CIE2000 bug. 40 | c1 = LabColor(lab_l=32.8911, lab_a=-53.0107, lab_b=-43.3182) 41 | c2 = LabColor(lab_l=77.1797, lab_a=25.5928, lab_b=17.9412) 42 | result = delta_e_cie2000(c1, c2) 43 | expected = 78.772 44 | self.assertAlmostEqual( 45 | result, 46 | expected, 47 | 3, 48 | "DeltaE CIE2000 formula error. " 49 | "Got %.3f, expected %.3f (diff: %.3f)." 50 | % (result, expected, result - expected), 51 | ) 52 | 53 | def test_cie2000_accuracy_3(self): 54 | """ 55 | Reference: 56 | "The CIEDE2000 Color-Difference Formula: Implementation Notes, 57 | Supplementary Test Data, and Mathematical Observations,", G. Sharma, 58 | W. Wu, E. N. Dalal, submitted to Color Research and Application, 59 | January 2004. http://www.ece.rochester.edu/~gsharma/ciede2000/ 60 | """ 61 | 62 | color1 = ( 63 | LabColor(lab_l=50.0000, lab_a=2.6772, lab_b=-79.7751), 64 | LabColor(lab_l=50.0000, lab_a=3.1571, lab_b=-77.2803), 65 | LabColor(lab_l=50.0000, lab_a=2.8361, lab_b=-74.0200), 66 | LabColor(lab_l=50.0000, lab_a=-1.3802, lab_b=-84.2814), 67 | LabColor(lab_l=50.0000, lab_a=-1.1848, lab_b=-84.8006), 68 | LabColor(lab_l=50.0000, lab_a=-0.9009, lab_b=-85.5211), 69 | LabColor(lab_l=50.0000, lab_a=0.0000, lab_b=0.0000), 70 | LabColor(lab_l=50.0000, lab_a=-1.0000, lab_b=2.0000), 71 | LabColor(lab_l=50.0000, lab_a=2.4900, lab_b=-0.0010), 72 | LabColor(lab_l=50.0000, lab_a=2.4900, lab_b=-0.0010), 73 | LabColor(lab_l=50.0000, lab_a=2.4900, lab_b=-0.0010), 74 | LabColor(lab_l=50.0000, lab_a=2.4900, lab_b=-0.0010), 75 | LabColor(lab_l=50.0000, lab_a=-0.0010, lab_b=2.4900), 76 | LabColor(lab_l=50.0000, lab_a=-0.0010, lab_b=2.4900), 77 | LabColor(lab_l=50.0000, lab_a=-0.0010, lab_b=2.4900), 78 | LabColor(lab_l=50.0000, lab_a=2.5000, lab_b=0.0000), 79 | LabColor(lab_l=50.0000, lab_a=2.5000, lab_b=0.0000), 80 | LabColor(lab_l=50.0000, lab_a=2.5000, lab_b=0.0000), 81 | LabColor(lab_l=50.0000, lab_a=2.5000, lab_b=0.0000), 82 | LabColor(lab_l=50.0000, lab_a=2.5000, lab_b=0.0000), 83 | LabColor(lab_l=50.0000, lab_a=2.5000, lab_b=0.0000), 84 | LabColor(lab_l=50.0000, lab_a=2.5000, lab_b=0.0000), 85 | LabColor(lab_l=50.0000, lab_a=2.5000, lab_b=0.0000), 86 | LabColor(lab_l=50.0000, lab_a=2.5000, lab_b=0.0000), 87 | LabColor(lab_l=60.2574, lab_a=-34.0099, lab_b=36.2677), 88 | LabColor(lab_l=63.0109, lab_a=-31.0961, lab_b=-5.8663), 89 | LabColor(lab_l=61.2901, lab_a=3.7196, lab_b=-5.3901), 90 | LabColor(lab_l=35.0831, lab_a=-44.1164, lab_b=3.7933), 91 | LabColor(lab_l=22.7233, lab_a=20.0904, lab_b=-46.6940), 92 | LabColor(lab_l=36.4612, lab_a=47.8580, lab_b=18.3852), 93 | LabColor(lab_l=90.8027, lab_a=-2.0831, lab_b=1.4410), 94 | LabColor(lab_l=90.9257, lab_a=-0.5406, lab_b=-0.9208), 95 | LabColor(lab_l=6.7747, lab_a=-0.2908, lab_b=-2.4247), 96 | LabColor(lab_l=2.0776, lab_a=0.0795, lab_b=-1.1350), 97 | ) 98 | color2 = ( 99 | LabColor(lab_l=50.0000, lab_a=0.0000, lab_b=-82.7485), 100 | LabColor(lab_l=50.0000, lab_a=0.0000, lab_b=-82.7485), 101 | LabColor(lab_l=50.0000, lab_a=0.0000, lab_b=-82.7485), 102 | LabColor(lab_l=50.0000, lab_a=0.0000, lab_b=-82.7485), 103 | LabColor(lab_l=50.0000, lab_a=0.0000, lab_b=-82.7485), 104 | LabColor(lab_l=50.0000, lab_a=0.0000, lab_b=-82.7485), 105 | LabColor(lab_l=50.0000, lab_a=-1.0000, lab_b=2.0000), 106 | LabColor(lab_l=50.0000, lab_a=0.0000, lab_b=0.0000), 107 | LabColor(lab_l=50.0000, lab_a=-2.4900, lab_b=0.0009), 108 | LabColor(lab_l=50.0000, lab_a=-2.4900, lab_b=0.0010), 109 | LabColor(lab_l=50.0000, lab_a=-2.4900, lab_b=0.0011), 110 | LabColor(lab_l=50.0000, lab_a=-2.4900, lab_b=0.0012), 111 | LabColor(lab_l=50.0000, lab_a=0.0009, lab_b=-2.4900), 112 | LabColor(lab_l=50.0000, lab_a=0.0010, lab_b=-2.4900), 113 | LabColor(lab_l=50.0000, lab_a=0.0011, lab_b=-2.4900), 114 | LabColor(lab_l=50.0000, lab_a=0.0000, lab_b=-2.5000), 115 | LabColor(lab_l=73.0000, lab_a=25.0000, lab_b=-18.0000), 116 | LabColor(lab_l=61.0000, lab_a=-5.0000, lab_b=29.0000), 117 | LabColor(lab_l=56.0000, lab_a=-27.0000, lab_b=-3.0000), 118 | LabColor(lab_l=58.0000, lab_a=24.0000, lab_b=15.0000), 119 | LabColor(lab_l=50.0000, lab_a=3.1736, lab_b=0.5854), 120 | LabColor(lab_l=50.0000, lab_a=3.2972, lab_b=0.0000), 121 | LabColor(lab_l=50.0000, lab_a=1.8634, lab_b=0.5757), 122 | LabColor(lab_l=50.0000, lab_a=3.2592, lab_b=0.3350), 123 | LabColor(lab_l=60.4626, lab_a=-34.1751, lab_b=39.4387), 124 | LabColor(lab_l=62.8187, lab_a=-29.7946, lab_b=-4.0864), 125 | LabColor(lab_l=61.4292, lab_a=2.2480, lab_b=-4.9620), 126 | LabColor(lab_l=35.0232, lab_a=-40.0716, lab_b=1.5901), 127 | LabColor(lab_l=23.0331, lab_a=14.9730, lab_b=-42.5619), 128 | LabColor(lab_l=36.2715, lab_a=50.5065, lab_b=21.2231), 129 | LabColor(lab_l=91.1528, lab_a=-1.6435, lab_b=0.0447), 130 | LabColor(lab_l=88.6381, lab_a=-0.8985, lab_b=-0.7239), 131 | LabColor(lab_l=5.8714, lab_a=-0.0985, lab_b=-2.2286), 132 | LabColor(lab_l=0.9033, lab_a=-0.0636, lab_b=-0.5514), 133 | ) 134 | diff = ( 135 | 2.0425, 136 | 2.8615, 137 | 3.4412, 138 | 1.0000, 139 | 1.0000, 140 | 1.0000, 141 | 2.3669, 142 | 2.3669, 143 | 7.1792, 144 | 7.1792, 145 | 7.2195, 146 | 7.2195, 147 | 4.8045, 148 | 4.8045, 149 | 4.7461, 150 | 4.3065, 151 | 27.1492, 152 | 22.8977, 153 | 31.9030, 154 | 19.4535, 155 | 1.0000, 156 | 1.0000, 157 | 1.0000, 158 | 1.0000, 159 | 1.2644, 160 | 1.2630, 161 | 1.8731, 162 | 1.8645, 163 | 2.0373, 164 | 1.4146, 165 | 1.4441, 166 | 1.5381, 167 | 0.6377, 168 | 0.9082, 169 | ) 170 | for l_set in zip(color1, color2, diff): 171 | result = delta_e_cie2000(l_set[0], l_set[1]) 172 | expected = l_set[2] 173 | self.assertAlmostEqual( 174 | result, 175 | expected, 176 | 4, 177 | "DeltaE CIE2000 formula error. " 178 | "Got %.4f, expected %.4f (diff: %.4f)." 179 | % (result, expected, result - expected), 180 | ) 181 | 182 | def test_cie1994_negative_square_root(self): 183 | """ 184 | Tests against a case where a negative square root in the delta_H 185 | calculation could happen. 186 | """ 187 | 188 | standard = LabColor(lab_l=0.9, lab_a=1, lab_b=1) 189 | sample = LabColor(lab_l=0.7, lab_a=0, lab_b=0) 190 | delta_e_cie1994(standard, sample) 191 | 192 | def test_cmc_negative_square_root(self): 193 | """ 194 | Tests against a case where a negative square root in the delta_H 195 | calculation could happen. 196 | """ 197 | 198 | standard = LabColor(lab_l=0.9, lab_a=1, lab_b=1) 199 | sample = LabColor(lab_l=0.7, lab_a=0, lab_b=0) 200 | delta_e_cmc(standard, sample) 201 | 202 | # noinspection PyArgumentEqualDefault 203 | def test_cmc_accuracy(self): 204 | # Test 2:1 205 | result = delta_e_cmc(self.color1, self.color2, pl=2, pc=1) 206 | expected = 1.443 207 | self.assertAlmostEqual( 208 | result, 209 | expected, 210 | 3, 211 | "DeltaE CMC (2:1) formula error. " 212 | "Got %.3f, expected %.3f (diff: %.3f)." 213 | % (result, expected, result - expected), 214 | ) 215 | 216 | # Test against 1:1 as well 217 | result = delta_e_cmc(self.color1, self.color2, pl=1, pc=1) 218 | expected = 1.482 219 | self.assertAlmostEqual( 220 | result, 221 | expected, 222 | 3, 223 | "DeltaE CMC (1:1) formula error. " 224 | "Got %.3f, expected %.3f (diff: %.3f)." 225 | % (result, expected, result - expected), 226 | ) 227 | 228 | # Testing atan H behavior. 229 | atan_color1 = LabColor(lab_l=69.417, lab_a=-12.612, lab_b=-11.271) 230 | atan_color2 = LabColor(lab_l=83.386, lab_a=39.426, lab_b=-17.525) 231 | result = delta_e_cmc(atan_color1, atan_color2) 232 | expected = 44.346 233 | self.assertAlmostEqual( 234 | result, 235 | expected, 236 | 3, 237 | "DeltaE CMC Atan test formula error. " 238 | "Got %.3f, expected %.3f (diff: %.3f)." 239 | % (result, expected, result - expected), 240 | ) 241 | 242 | def test_cie1976_accuracy(self): 243 | result = delta_e_cie1976(self.color1, self.color2) 244 | expected = 2.151 245 | self.assertAlmostEqual( 246 | result, 247 | expected, 248 | 3, 249 | "DeltaE CIE1976 formula error. " 250 | "Got %.3f, expected %.3f (diff: %.3f)." 251 | % (result, expected, result - expected), 252 | ) 253 | 254 | def test_cie1994_accuracy_graphic_arts(self): 255 | result = delta_e_cie1994(self.color1, self.color2) 256 | expected = 1.249 257 | self.assertAlmostEqual( 258 | result, 259 | expected, 260 | 3, 261 | "DeltaE CIE1994 (graphic arts) formula error. " 262 | "Got %.3f, expected %.3f (diff: %.3f)." 263 | % (result, expected, result - expected), 264 | ) 265 | 266 | def test_cie1994_accuracy_textiles(self): 267 | result = delta_e_cie1994(self.color1, self.color2, K_1=0.048, K_2=0.014, K_L=2) 268 | expected = 1.204 269 | self.assertAlmostEqual( 270 | result, 271 | expected, 272 | 3, 273 | "DeltaE CIE1994 (textiles) formula error. " 274 | "Got %.3f, expected %.3f (diff: %.3f)." 275 | % (result, expected, result - expected), 276 | ) 277 | 278 | def test_cie1994_domain_error(self): 279 | # These values are from ticket 98 in regards to a CIE1995 280 | # domain error exception being raised. 281 | c1 = LabColor(lab_l=50, lab_a=0, lab_b=0) 282 | c2 = LabColor(lab_l=50, lab_a=-1, lab_b=2) 283 | try: 284 | delta_e_cie1994(c1, c2) 285 | except ValueError: 286 | self.fail("DeltaE CIE1994 domain error.") 287 | 288 | def test_non_lab_color(self): 289 | other_color = sRGBColor(1.0, 0.5, 0.3) 290 | self.assertRaises(ValueError, delta_e_cie2000, self.color1, other_color) 291 | -------------------------------------------------------------------------------- /tests/test_color_objects.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Various tests for color objects. 4 | """ 5 | 6 | import unittest 7 | 8 | from colormath.color_conversions import convert_color 9 | from colormath.color_objects import ( 10 | SpectralColor, 11 | XYZColor, 12 | xyYColor, 13 | LabColor, 14 | LuvColor, 15 | LCHabColor, 16 | LCHuvColor, 17 | sRGBColor, 18 | HSLColor, 19 | HSVColor, 20 | CMYColor, 21 | CMYKColor, 22 | AdobeRGBColor, 23 | AppleRGBColor, 24 | IPTColor, 25 | ) 26 | 27 | 28 | class BaseColorConversionTest(unittest.TestCase): 29 | """ 30 | All color conversion tests should inherit from this class. Has some 31 | convenience methods for re-use. 32 | """ 33 | 34 | # noinspection PyPep8Naming 35 | def assertColorMatch(self, conv, std): 36 | """ 37 | Checks a converted color against an expected standard. 38 | 39 | :param conv: The converted color object. 40 | :param std: The object to use as a standard for comparison. 41 | """ 42 | 43 | self.assertEqual(conv.__class__, std.__class__) 44 | attribs = std.VALUES 45 | for attrib in attribs: 46 | conv_value = getattr(conv, attrib) 47 | std_value = getattr(std, attrib) 48 | self.assertAlmostEqual( 49 | conv_value, 50 | std_value, 51 | 3, 52 | "%s is %s, expected %s" % (attrib, conv_value, std_value), 53 | ) 54 | 55 | 56 | class SpectralConversionTestCase(BaseColorConversionTest): 57 | def setUp(self): 58 | """ 59 | While it is possible to specify the entire spectral color using 60 | positional arguments, set this thing up with keywords for the ease of 61 | manipulation. 62 | """ 63 | 64 | color = SpectralColor( 65 | spec_380nm=0.0600, 66 | spec_390nm=0.0600, 67 | spec_400nm=0.0641, 68 | spec_410nm=0.0654, 69 | spec_420nm=0.0645, 70 | spec_430nm=0.0605, 71 | spec_440nm=0.0562, 72 | spec_450nm=0.0543, 73 | spec_460nm=0.0537, 74 | spec_470nm=0.0541, 75 | spec_480nm=0.0559, 76 | spec_490nm=0.0603, 77 | spec_500nm=0.0651, 78 | spec_510nm=0.0680, 79 | spec_520nm=0.0705, 80 | spec_530nm=0.0736, 81 | spec_540nm=0.0772, 82 | spec_550nm=0.0809, 83 | spec_560nm=0.0870, 84 | spec_570nm=0.0990, 85 | spec_580nm=0.1128, 86 | spec_590nm=0.1251, 87 | spec_600nm=0.1360, 88 | spec_610nm=0.1439, 89 | spec_620nm=0.1511, 90 | spec_630nm=0.1590, 91 | spec_640nm=0.1688, 92 | spec_650nm=0.1828, 93 | spec_660nm=0.1996, 94 | spec_670nm=0.2187, 95 | spec_680nm=0.2397, 96 | spec_690nm=0.2618, 97 | spec_700nm=0.2852, 98 | spec_710nm=0.2500, 99 | spec_720nm=0.2400, 100 | spec_730nm=0.2300, 101 | ) 102 | self.color = color 103 | 104 | def test_conversion_to_xyz(self): 105 | xyz = convert_color(self.color, XYZColor) 106 | self.assertColorMatch(xyz, XYZColor(0.115, 0.099, 0.047)) 107 | 108 | def test_conversion_to_xyz_with_negatives(self): 109 | """ 110 | This has negative spectral values, which should never happen. Just 111 | clamp these to 0.0 instead of running into the domain errors. A badly 112 | or uncalibrated spectro can sometimes report negative values. 113 | """ 114 | 115 | self.color.spec_530nm = -0.0736 116 | # TODO: Convert here. 117 | 118 | def test_convert_to_self(self): 119 | same_color = convert_color(self.color, SpectralColor) 120 | self.assertEqual(self.color, same_color) 121 | 122 | 123 | class XYZConversionTestCase(BaseColorConversionTest): 124 | def setUp(self): 125 | self.color = XYZColor(0.1, 0.2, 0.3) 126 | 127 | def test_conversion_to_xyy(self): 128 | xyy = convert_color(self.color, xyYColor) 129 | self.assertColorMatch(xyy, xyYColor(0.167, 0.333, 0.200)) 130 | 131 | def test_conversion_to_lab(self): 132 | lab = convert_color(self.color, LabColor) 133 | self.assertColorMatch(lab, LabColor(51.837, -57.486, -25.780)) 134 | 135 | def test_conversion_to_rgb(self): 136 | # Picked a set of XYZ coordinates that would return a good RGB value. 137 | self.color = XYZColor(0.300, 0.200, 0.300) 138 | rgb = convert_color(self.color, sRGBColor) 139 | self.assertColorMatch(rgb, sRGBColor(0.715, 0.349, 0.663)) 140 | 141 | def test_conversion_to_apple_rgb(self): 142 | self.color = XYZColor(0.0157, 0.0191, 0.0331) 143 | rgb = convert_color(self.color, AppleRGBColor) 144 | self.assertColorMatch(rgb, AppleRGBColor(0.0411, 0.1214, 0.1763)) 145 | 146 | def test_conversion_to_adobe_rgb(self): 147 | self.color = XYZColor(0.2553, 0.1125, 0.0011) 148 | rgb = convert_color(self.color, AdobeRGBColor) 149 | # This ends up getting clamped. 150 | self.assertColorMatch(rgb, AdobeRGBColor(0.6828, 0.0, 0.0)) 151 | 152 | def test_conversion_to_luv(self): 153 | luv = convert_color(self.color, LuvColor) 154 | self.assertColorMatch(luv, LuvColor(51.837, -73.561, -25.657)) 155 | 156 | def test_conversion_to_ipt(self): 157 | self.color.set_illuminant("D65") 158 | ipt = convert_color(self.color, IPTColor) 159 | self.assertColorMatch(ipt, IPTColor(0.5063, -0.3183, -0.1160)) 160 | 161 | def test_convert_to_self(self): 162 | same_color = convert_color(self.color, XYZColor) 163 | self.assertEqual(self.color, same_color) 164 | 165 | 166 | # noinspection PyPep8Naming 167 | class xyYConversionTestCase(BaseColorConversionTest): 168 | def setUp(self): 169 | self.color = xyYColor(0.167, 0.333, 0.200) 170 | 171 | def test_conversion_to_xyz(self): 172 | xyz = convert_color(self.color, XYZColor) 173 | self.assertColorMatch(xyz, XYZColor(0.100, 0.200, 0.300)) 174 | 175 | def test_convert_to_self(self): 176 | same_color = convert_color(self.color, xyYColor) 177 | self.assertEqual(self.color, same_color) 178 | 179 | 180 | class LabConversionTestCase(BaseColorConversionTest): 181 | def setUp(self): 182 | self.color = LabColor(1.807, -3.749, -2.547) 183 | 184 | def test_conversion_to_xyz(self): 185 | xyz = convert_color(self.color, XYZColor) 186 | self.assertColorMatch(xyz, XYZColor(0.001, 0.002, 0.003)) 187 | 188 | def test_conversion_to_lchab(self): 189 | lch = convert_color(self.color, LCHabColor) 190 | self.assertColorMatch(lch, LCHabColor(1.807, 4.532, 214.191)) 191 | 192 | def test_convert_to_self(self): 193 | same_color = convert_color(self.color, LabColor) 194 | self.assertEqual(self.color, same_color) 195 | 196 | 197 | class LuvConversionTestCase(BaseColorConversionTest): 198 | def setUp(self): 199 | self.color = LuvColor(1.807, -2.564, -0.894) 200 | 201 | def test_conversion_to_xyz(self): 202 | xyz = convert_color(self.color, XYZColor) 203 | self.assertColorMatch(xyz, XYZColor(0.001, 0.002, 0.003)) 204 | 205 | def test_conversion_to_lchuv(self): 206 | lch = convert_color(self.color, LCHuvColor) 207 | self.assertColorMatch(lch, LCHuvColor(1.807, 2.715, 199.222)) 208 | 209 | def test_convert_to_self(self): 210 | same_color = convert_color(self.color, LuvColor) 211 | self.assertEqual(self.color, same_color) 212 | 213 | 214 | # noinspection PyAttributeOutsideInit,PyPep8Naming 215 | class LCHabConversionTestCase(BaseColorConversionTest): 216 | def setUp(self): 217 | self.color = LCHabColor(1.807, 4.532, 214.191) 218 | 219 | def test_conversion_to_lab(self): 220 | lab = convert_color(self.color, LabColor) 221 | self.assertColorMatch(lab, LabColor(1.807, -3.749, -2.547)) 222 | 223 | def test_conversion_to_rgb_zero_div(self): 224 | """ 225 | The formula I grabbed for LCHuv to XYZ had a zero division error in it 226 | if the L coord was 0. Also check against LCHab in case. 227 | """ 228 | 229 | lchab = LCHabColor(0.0, 0.0, 0.0) 230 | rgb = convert_color(lchab, sRGBColor) 231 | self.assertColorMatch(rgb, sRGBColor(0.0, 0.0, 0.0)) 232 | 233 | def test_to_rgb_domain_error(self): 234 | """ 235 | Tests for a bug resulting in a domain error with LCH->Adobe RGB. 236 | 237 | See: https://github.com/gtaylor/python-colormath/issues/49 238 | """ 239 | 240 | lchab = LCHabColor(40.0, 104.0, 40.0) 241 | _rgb = convert_color(lchab, AdobeRGBColor) # noqa 242 | 243 | def test_convert_to_self(self): 244 | same_color = convert_color(self.color, LCHabColor) 245 | self.assertEqual(self.color, same_color) 246 | 247 | 248 | class LCHuvConversionTestCase(BaseColorConversionTest): 249 | def setUp(self): 250 | self.color = LCHuvColor(1.807, 2.715, 199.228) 251 | 252 | def test_conversion_to_luv(self): 253 | luv = convert_color(self.color, LuvColor) 254 | self.assertColorMatch(luv, LuvColor(1.807, -2.564, -0.894)) 255 | 256 | def test_conversion_to_rgb_zero_div(self): 257 | """ 258 | The formula I grabbed for LCHuv to XYZ had a zero division error in it 259 | if the L coord was 0. Check against that here. 260 | 261 | Issue #13 in the Google Code tracker. 262 | """ 263 | 264 | lchuv = LCHuvColor(0.0, 0.0, 0.0) 265 | rgb = convert_color(lchuv, sRGBColor) 266 | self.assertColorMatch(rgb, sRGBColor(0.0, 0.0, 0.0)) 267 | 268 | def test_convert_to_self(self): 269 | same_color = convert_color(self.color, LCHuvColor) 270 | self.assertEqual(self.color, same_color) 271 | 272 | 273 | class RGBConversionTestCase(BaseColorConversionTest): 274 | def setUp(self): 275 | self.color = sRGBColor(0.482, 0.784, 0.196) 276 | 277 | def test_channel_clamping(self): 278 | high_r = sRGBColor(1.482, 0.2, 0.3) 279 | self.assertEqual(high_r.clamped_rgb_r, 1.0) 280 | self.assertEqual(high_r.clamped_rgb_g, high_r.rgb_g) 281 | self.assertEqual(high_r.clamped_rgb_b, high_r.rgb_b) 282 | 283 | low_r = sRGBColor(-0.1, 0.2, 0.3) 284 | self.assertEqual(low_r.clamped_rgb_r, 0.0) 285 | self.assertEqual(low_r.clamped_rgb_g, low_r.rgb_g) 286 | self.assertEqual(low_r.clamped_rgb_b, low_r.rgb_b) 287 | 288 | high_g = sRGBColor(0.2, 1.482, 0.3) 289 | self.assertEqual(high_g.clamped_rgb_r, high_g.rgb_r) 290 | self.assertEqual(high_g.clamped_rgb_g, 1.0) 291 | self.assertEqual(high_g.clamped_rgb_b, high_g.rgb_b) 292 | 293 | low_g = sRGBColor(0.2, -0.1, 0.3) 294 | self.assertEqual(low_g.clamped_rgb_r, low_g.rgb_r) 295 | self.assertEqual(low_g.clamped_rgb_g, 0.0) 296 | self.assertEqual(low_g.clamped_rgb_b, low_g.rgb_b) 297 | 298 | high_b = sRGBColor(0.1, 0.2, 1.482) 299 | self.assertEqual(high_b.clamped_rgb_r, high_b.rgb_r) 300 | self.assertEqual(high_b.clamped_rgb_g, high_b.rgb_g) 301 | self.assertEqual(high_b.clamped_rgb_b, 1.0) 302 | 303 | low_b = sRGBColor(0.1, 0.2, -0.1) 304 | self.assertEqual(low_b.clamped_rgb_r, low_b.rgb_r) 305 | self.assertEqual(low_b.clamped_rgb_g, low_b.rgb_g) 306 | self.assertEqual(low_b.clamped_rgb_b, 0.0) 307 | 308 | def test_to_xyz_and_back(self): 309 | xyz = convert_color(self.color, XYZColor) 310 | rgb = convert_color(xyz, sRGBColor) 311 | self.assertColorMatch(rgb, self.color) 312 | 313 | def test_conversion_to_hsl_max_r(self): 314 | color = sRGBColor(255, 123, 50, is_upscaled=True) 315 | hsl = convert_color(color, HSLColor) 316 | self.assertColorMatch(hsl, HSLColor(21.366, 1.000, 0.598)) 317 | 318 | def test_conversion_to_hsl_max_g(self): 319 | color = sRGBColor(123, 255, 50, is_upscaled=True) 320 | hsl = convert_color(color, HSLColor) 321 | self.assertColorMatch(hsl, HSLColor(98.634, 1.000, 0.598)) 322 | 323 | def test_conversion_to_hsl_max_b(self): 324 | color = sRGBColor(0.482, 0.482, 1.0) 325 | hsl = convert_color(color, HSLColor) 326 | self.assertColorMatch(hsl, HSLColor(240.000, 1.000, 0.741)) 327 | 328 | def test_conversion_to_hsl_gray(self): 329 | color = sRGBColor(0.482, 0.482, 0.482) 330 | hsl = convert_color(color, HSLColor) 331 | self.assertColorMatch(hsl, HSLColor(0.000, 0.000, 0.482)) 332 | 333 | def test_conversion_to_hsv(self): 334 | hsv = convert_color(self.color, HSVColor) 335 | self.assertColorMatch(hsv, HSVColor(90.816, 0.750, 0.784)) 336 | 337 | def test_conversion_to_cmy(self): 338 | cmy = convert_color(self.color, CMYColor) 339 | self.assertColorMatch(cmy, CMYColor(0.518, 0.216, 0.804)) 340 | 341 | def test_srgb_conversion_to_xyz_d50(self): 342 | """ 343 | sRGB's native illuminant is D65. Test the XYZ adaptations by setting 344 | a target illuminant to something other than D65. 345 | """ 346 | 347 | xyz = convert_color(self.color, XYZColor, target_illuminant="D50") 348 | self.assertColorMatch(xyz, XYZColor(0.313, 0.460, 0.082)) 349 | 350 | def test_srgb_conversion_to_xyz_d65(self): 351 | """ 352 | sRGB's native illuminant is D65. This is a straightforward conversion. 353 | """ 354 | 355 | xyz = convert_color(self.color, XYZColor) 356 | self.assertColorMatch(xyz, XYZColor(0.294, 0.457, 0.103)) 357 | 358 | def test_adobe_conversion_to_xyz_d65(self): 359 | """ 360 | Adobe RGB's native illuminant is D65, like sRGB's. However, sRGB uses 361 | different conversion math that uses gamma, so test the alternate logic 362 | route for non-sRGB RGB colors. 363 | """ 364 | 365 | adobe = AdobeRGBColor(0.482, 0.784, 0.196) 366 | xyz = convert_color(adobe, XYZColor) 367 | self.assertColorMatch(xyz, XYZColor(0.230, 0.429, 0.074)) 368 | 369 | def test_conversion_through_rgb(self): 370 | """ 371 | Make sure our convenience RGB tracking feature is working. For example, 372 | going from XYZ->HSL via Adobe RGB, then taking that HSL object and 373 | converting back to XYZ should also use Adobe RGB (instead of the 374 | default of sRGB). 375 | """ 376 | 377 | xyz = convert_color(self.color, XYZColor) 378 | hsl = convert_color(xyz, HSLColor, through_rgb_type=AdobeRGBColor) 379 | # Notice how we don't have to pass through_rgb_type explicitly. 380 | xyz2 = convert_color(hsl, XYZColor) 381 | self.assertColorMatch(xyz, xyz2) 382 | 383 | def test_adobe_conversion_to_xyz_d50(self): 384 | """ 385 | Adobe RGB's native illuminant is D65, so an adaptation matrix is 386 | involved here. However, the math for sRGB and all other RGB types is 387 | different, so test all of the other types with an adaptation matrix 388 | here. 389 | """ 390 | 391 | adobe = AdobeRGBColor(0.482, 0.784, 0.196) 392 | xyz = convert_color(adobe, XYZColor, target_illuminant="D50") 393 | self.assertColorMatch(xyz, XYZColor(0.247, 0.431, 0.060)) 394 | 395 | def test_convert_to_self(self): 396 | same_color = convert_color(self.color, sRGBColor) 397 | self.assertEqual(self.color, same_color) 398 | 399 | def test_get_rgb_hex(self): 400 | hex_str = self.color.get_rgb_hex() 401 | self.assertEqual(hex_str, "#7bc832", "sRGB to hex conversion failed") 402 | 403 | def test_set_from_rgb_hex(self): 404 | rgb = sRGBColor.new_from_rgb_hex("#7bc832") 405 | self.assertColorMatch(rgb, sRGBColor(0.482, 0.784, 0.196)) 406 | 407 | 408 | class HSLConversionTestCase(BaseColorConversionTest): 409 | def setUp(self): 410 | self.color = HSLColor(200.0, 0.400, 0.500) 411 | 412 | def test_conversion_to_rgb(self): 413 | rgb = convert_color(self.color, sRGBColor) 414 | self.assertColorMatch(rgb, sRGBColor(0.300, 0.567, 0.700)) 415 | # Make sure this converts to AdobeRGBColor instead of sRGBColor. 416 | adobe_rgb = convert_color(self.color, AdobeRGBColor) 417 | self.assertIsInstance(adobe_rgb, AdobeRGBColor) 418 | 419 | def test_convert_to_self(self): 420 | same_color = convert_color(self.color, HSLColor) 421 | self.assertEqual(self.color, same_color) 422 | 423 | 424 | class HSVConversionTestCase(BaseColorConversionTest): 425 | def setUp(self): 426 | self.color = HSVColor(91.0, 0.750, 0.784) 427 | 428 | def test_conversion_to_rgb(self): 429 | rgb = convert_color(self.color, sRGBColor) 430 | self.assertColorMatch(rgb, sRGBColor(0.480, 0.784, 0.196)) 431 | 432 | def test_convert_to_self(self): 433 | same_color = convert_color(self.color, HSVColor) 434 | self.assertEqual(self.color, same_color) 435 | 436 | 437 | class CMYConversionTestCase(BaseColorConversionTest): 438 | def setUp(self): 439 | self.color = CMYColor(0.518, 0.216, 0.804) 440 | 441 | def test_conversion_to_cmyk(self): 442 | cmyk = convert_color(self.color, CMYKColor) 443 | self.assertColorMatch(cmyk, CMYKColor(0.385, 0.000, 0.750, 0.216)) 444 | 445 | def test_conversion_to_rgb(self): 446 | rgb = convert_color(self.color, sRGBColor) 447 | self.assertColorMatch(rgb, sRGBColor(0.482, 0.784, 0.196)) 448 | 449 | def test_convert_to_self(self): 450 | same_color = convert_color(self.color, CMYColor) 451 | self.assertEqual(self.color, same_color) 452 | 453 | 454 | class CMYKConversionTestCase(BaseColorConversionTest): 455 | def setUp(self): 456 | self.color = CMYKColor(0.385, 0.000, 0.750, 0.216) 457 | 458 | def test_conversion_to_cmy(self): 459 | cmy = convert_color(self.color, CMYColor) 460 | self.assertColorMatch(cmy, CMYColor(0.518, 0.216, 0.804)) 461 | 462 | def test_convert_to_self(self): 463 | same_color = convert_color(self.color, CMYKColor) 464 | self.assertEqual(self.color, same_color) 465 | 466 | 467 | class IPTConversionTestCase(BaseColorConversionTest): 468 | def setUp(self): 469 | self.color = IPTColor(0.5, 0.5, 0.5) 470 | 471 | def test_convert_so_self(self): 472 | same_color = convert_color(self.color, IPTColor) 473 | self.assertEqual(self.color, same_color) 474 | 475 | def test_convert_to_XYZ(self): 476 | xyz = convert_color(self.color, XYZColor) 477 | self.assertColorMatch( 478 | xyz, XYZColor(0.4497, 0.2694, 0.0196, illuminant="d65", observer="2") 479 | ) 480 | 481 | def test_consistency(self): 482 | xyz = convert_color(self.color, XYZColor) 483 | same_color = convert_color(xyz, IPTColor) 484 | self.assertColorMatch(self.color, same_color) 485 | 486 | def test_illuminant_guard(self): 487 | xyz = XYZColor(1, 1, 1, illuminant="d50", observer="2") 488 | 489 | def _ipt_conversion(): 490 | return convert_color(xyz, IPTColor) 491 | 492 | self.assertRaises(ValueError, _ipt_conversion) 493 | 494 | def test_observer_guard(self): 495 | xyz = XYZColor(1, 1, 1, illuminant="d65", observer="10") 496 | 497 | def _ipt_conversion(): 498 | return convert_color(xyz, IPTColor) 499 | 500 | self.assertRaises(ValueError, _ipt_conversion) 501 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # Tox (http://tox.testrun.org/) is a tool for running tests 2 | # in multiple virtualenvs. This configuration file will run the 3 | # test suite on all supported python versions. To use it, "pip install tox" 4 | # and then run "tox" from this directory. 5 | 6 | [tox] 7 | envlist = py27, py33, py34 8 | 9 | [testenv] 10 | commands = nosetests -s tests 11 | deps = 12 | nose 13 | numpy 14 | networkx 15 | --------------------------------------------------------------------------------