#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
RGB Colourspace Volume Computation
==================================
Defines various RGB colourspace volume computation objects:
- :func:`RGB_colourspace_limits`
- :func:`RGB_colourspace_volume_MonteCarlo`
- :func:`RGB_colourspace_pointer_gamut_coverage_MonteCarlo`
- :func:`RGB_colourspace_visible_spectrum_coverage_MonteCarlo`
See Also
--------
`RGB Colourspace Volume Computation IPython Notebook
<http://nbviewer.ipython.org/github/colour-science/colour-ipython/blob/master/notebooks/volume/rgb.ipynb>`_ # noqa
"""
from __future__ import division, unicode_literals
import itertools
import multiprocessing
import numpy as np
from colour.algebra import random_triplet_generator
from colour.colorimetry import ILLUMINANTS
from colour.models import (
Lab_to_XYZ,
RGB_to_XYZ,
XYZ_to_Lab,
XYZ_to_RGB)
from colour.utilities import is_scipy_installed
from colour.volume import is_within_pointer_gamut, is_within_visible_spectrum
__author__ = 'Colour Developers'
__copyright__ = 'Copyright (C) 2013 - 2014 - Colour Developers'
__license__ = 'New BSD License - http://opensource.org/licenses/BSD-3-Clause'
__maintainer__ = 'Colour Developers'
__email__ = 'colour-science@googlegroups.com'
__status__ = 'Production'
__all__ = ['sample_RGB_colourspace_volume_MonteCarlo',
'RGB_colourspace_limits',
'RGB_colourspace_volume_MonteCarlo',
'RGB_colourspace_pointer_gamut_coverage_MonteCarlo',
'RGB_colourspace_visible_spectrum_coverage_MonteCarlo']
def _wrapper_RGB_colourspace_volume_MonteCarlo(args):
"""
Convenient wrapper to be able to call
:func:`sample_RGB_colourspace_volume_MonteCarlo`: definition with multiple
arguments.
Parameters
----------
\*args : \*
Arguments.
Returns
-------
integer
Inside *RGB* colourspace volume samples count.
"""
return sample_RGB_colourspace_volume_MonteCarlo(*args)
def sample_RGB_colourspace_volume_MonteCarlo(
colourspace,
samples=10e6,
limits=np.array([[0, 100], [-150, 150], [-150, 150]]),
illuminant_Lab=ILLUMINANTS.get(
'CIE 1931 2 Degree Standard Observer').get('D50'),
chromatic_adaptation_method='CAT02',
random_generator=random_triplet_generator,
random_state=None):
"""
Randomly samples the *Lab* colourspace volume and returns the ratio of
samples within the given *RGB* colourspace volume.
Parameters
----------
colourspace : RGB_Colourspace
*RGB* colourspace to compute the volume of.
samples : numeric, optional
Samples count.
limits : array_like, optional
*Lab* colourspace volume.
illuminant_Lab : array_like, optional
*Lab* colourspace *illuminant* chromaticity coordinates.
chromatic_adaptation_method : unicode, optional
**{'CAT02', 'XYZ Scaling', 'Von Kries', 'Bradford', 'Sharp',
'Fairchild, 'CMCCAT97', 'CMCCAT2000', 'CAT02_BRILL_CAT', 'Bianco',
'Bianco PC'}**,
*Chromatic adaptation* method.
random_generator : generator, optional
Random triplet generator providing the random samples within the *Lab*
colourspace volume.
random_state : RandomState, optional
Mersenne Twister pseudo-random number generator to use in the random
number generator.
Returns
-------
integer
Within *RGB* colourspace volume samples count.
Notes
-----
The doctest is assuming that :func:`np.random.RandomState` definition will
return the same sequence no matter which *OS* or *Python* version is used.
There is however no formal promise about the *prng* sequence
reproducibility of either *Python or *Numpy* implementations: Laurent.
(2012). Reproducibility of python pseudo-random numbers across systems and
versions? Retrieved January 20, 2015, from
http://stackoverflow.com/questions/8786084/reproducibility-of-python-pseudo-random-numbers-across-systems-and-versions # noqa
Examples
--------
>>> from colour import sRGB_COLOURSPACE as sRGB
>>> prng = np.random.RandomState(2)
>>> sample_RGB_colourspace_volume_MonteCarlo(sRGB, 10e3, random_state=prng) # noqa # doctest: +ELLIPSIS
9...
"""
random_state = (random_state
if random_state is not None else
np.random.RandomState())
Lab = np.asarray(list(random_generator(samples, limits, random_state)))
RGB = XYZ_to_RGB(Lab_to_XYZ(Lab, illuminant_Lab),
illuminant_Lab,
colourspace.whitepoint,
colourspace.XYZ_to_RGB_matrix,
chromatic_adaptation_transform=(
chromatic_adaptation_method))
RGB_w = RGB[np.logical_and(np.min(RGB, axis=-1) >= 0,
np.max(RGB, axis=-1) <= 1)]
return len(RGB_w)
[docs]def RGB_colourspace_limits(colourspace,
illuminant=ILLUMINANTS.get(
'CIE 1931 2 Degree Standard Observer').get(
'D50')):
"""
Computes given *RGB* colourspace volume limits in *Lab* colourspace.
Parameters
----------
colourspace : RGB_Colourspace
*RGB* colourspace to compute the volume of.
illuminant_Lab : array_like, optional
*Lab* colourspace *illuminant* chromaticity coordinates.
Returns
-------
ndarray
*RGB* colourspace volume limits.
Examples
--------
>>> from colour import sRGB_COLOURSPACE as sRGB
>>> RGB_colourspace_limits(sRGB) # doctest: +ELLIPSIS
array([[ 0... , 100... ],
[ -79.2263741..., 94.6657491...],
[-114.7846271..., 96.7135199...]])
"""
Lab = []
for combination in list(itertools.product([0, 1], repeat=3)):
Lab.append(XYZ_to_Lab(RGB_to_XYZ(combination,
colourspace.whitepoint,
illuminant,
colourspace.RGB_to_XYZ_matrix)))
Lab = np.array(Lab)
limits = []
for i in np.arange(3):
limits.append((np.min(Lab[..., i]), np.max(Lab[..., i])))
return np.array(limits)
def RGB_colourspace_volume_MonteCarlo(
colourspace,
samples=10e6,
limits=np.array([[0, 100], [-150, 150], [-150, 150]]),
illuminant_Lab=ILLUMINANTS.get(
'CIE 1931 2 Degree Standard Observer').get('D50'),
chromatic_adaptation_method='CAT02',
random_generator=random_triplet_generator,
random_state=None,
processes=None):
"""
Performs given *RGB* colourspace volume computation using *Monte Carlo*
method and multiprocessing.
Parameters
----------
colourspace : RGB_Colourspace
*RGB* colourspace to compute the volume of.
samples : numeric, optional
Samples count.
limits : array_like, optional
*Lab* colourspace volume.
illuminant_Lab : array_like, optional
*Lab* colourspace *illuminant* chromaticity coordinates.
chromatic_adaptation_method : unicode, optional
**{'CAT02', 'XYZ Scaling', 'Von Kries', 'Bradford', 'Sharp',
'Fairchild, 'CMCCAT97', 'CMCCAT2000', 'CAT02_BRILL_CAT', 'Bianco',
'Bianco PC'}**,
*Chromatic adaptation* method.
random_generator : generator, optional
Random triplet generator providing the random samples within the *Lab*
colourspace volume.
random_state : RandomState, optional
Mersenne Twister pseudo-random number generator to use in the random
number generator.
processes : integer, optional
Processes count, default to :func:`multiprocessing.cpu_count`
definition.
Returns
-------
float
*RGB* colourspace volume.
Notes
-----
The doctest is assuming that :func:`np.random.RandomState` definition will
return the same sequence no matter which *OS* or *Python* version is used.
There is however no formal promise about the *prng* sequence
reproducibility of either *Python or *Numpy* implementations: Laurent.
(2012). Reproducibility of python pseudo-random numbers across systems and
versions? Retrieved January 20, 2015, from
http://stackoverflow.com/questions/8786084/reproducibility-of-python-pseudo-random-numbers-across-systems-and-versions # noqa
Examples
--------
>>> from colour import sRGB_COLOURSPACE as sRGB
>>> prng = np.random.RandomState(2)
>>> processes = 1
>>> RGB_colourspace_volume_MonteCarlo( # doctest: +ELLIPSIS
... sRGB, 10e3, random_state=prng, processes=processes)
859...
"""
cpu_count = processes if processes else multiprocessing.cpu_count()
pool = multiprocessing.Pool(processes=cpu_count)
process_samples = int(np.round(samples / cpu_count))
arguments = [colourspace,
process_samples,
limits,
illuminant_Lab,
chromatic_adaptation_method,
random_generator,
random_state]
results = pool.map(_wrapper_RGB_colourspace_volume_MonteCarlo,
[arguments for _ in range(cpu_count)])
Lab_volume = np.product([np.sum(np.abs(x)) for x in limits])
return Lab_volume * np.sum(results) / (process_samples * cpu_count)
def RGB_colourspace_volume_coverage_MonteCarlo(
colourspace,
coverage_sampler,
samples=10e6,
random_generator=random_triplet_generator,
random_state=None):
"""
Returns given *RGB* colourspace percentage coverage of an arbitrary volume.
Parameters
----------
colourspace : RGB_Colourspace
*RGB* colourspace to compute the volume coverage percentage.
coverage_sampler : object
Python object responsible for checking the volume coverage.
samples : numeric, optional
Samples count.
random_generator : generator, optional
Random triplet generator providing the random samples.
random_state : RandomState, optional
Mersenne Twister pseudo-random number generator to use in the random
number generator.
Returns
-------
float
Percentage coverage of volume.
Notes
-----
- This definition requires *scipy* to be installed.
Examples
--------
>>> from colour import sRGB_COLOURSPACE as sRGB
>>> prng = np.random.RandomState(2)
>>> RGB_colourspace_volume_coverage_MonteCarlo( # doctest: +ELLIPSIS
... sRGB,
... is_within_pointer_gamut,
... 10e3,
... random_state=prng)
83...
"""
if is_scipy_installed(raise_exception=True):
random_state = (random_state
if random_state is not None else
np.random.RandomState())
# TODO: Investigate for generator yielding directly a ndarray.
XYZ = np.asarray(list(random_generator(
samples, random_state=random_state)))
XYZ_vs = XYZ[coverage_sampler(XYZ)]
RGB = XYZ_to_RGB(XYZ_vs,
colourspace.whitepoint,
colourspace.whitepoint,
colourspace.XYZ_to_RGB_matrix)
RGB_c = RGB[np.logical_and(np.min(RGB, axis=-1) >= 0,
np.max(RGB, axis=-1) <= 1)]
return 100 * RGB_c.size / XYZ_vs.size
[docs]def RGB_colourspace_pointer_gamut_coverage_MonteCarlo(
colourspace,
samples=10e6,
random_generator=random_triplet_generator,
random_state=None):
"""
Returns given *RGB* colourspace percentage coverage of Pointer's Gamut
volume using *Monte Carlo* method.
Parameters
----------
colourspace : RGB_Colourspace
*RGB* colourspace to compute the Pointer's Gamut coverage percentage.
samples : numeric, optional
Samples count.
random_generator : generator, optional
Random triplet generator providing the random samples.
random_state : RandomState, optional
Mersenne Twister pseudo-random number generator to use in the random
number generator.
Returns
-------
float
Percentage coverage of Pointer's Gamut volume.
Notes
-----
- This definition requires *scipy* to be installed.
Examples
--------
>>> from colour import sRGB_COLOURSPACE as sRGB
>>> prng = np.random.RandomState(2)
>>> RGB_colourspace_pointer_gamut_coverage_MonteCarlo(
... sRGB,
... 10e3,
... random_state=prng) # doctest: +ELLIPSIS
83...
"""
return RGB_colourspace_volume_coverage_MonteCarlo(
colourspace,
is_within_pointer_gamut,
samples,
random_generator,
random_state)
[docs]def RGB_colourspace_visible_spectrum_coverage_MonteCarlo(
colourspace,
samples=10e6,
random_generator=random_triplet_generator,
random_state=None):
"""
Returns given *RGB* colourspace percentage coverage of visible spectrum
volume using *Monte Carlo* method.
Parameters
----------
colourspace : RGB_Colourspace
*RGB* colourspace to compute the visible spectrum coverage percentage.
samples : numeric, optional
Samples count.
random_generator : generator, optional
Random triplet generator providing the random samples.
random_state : RandomState, optional
Mersenne Twister pseudo-random number generator to use in the random
number generator.
Returns
-------
float
Percentage coverage of visible spectrum volume.
Notes
-----
- This definition requires *scipy* to be installed.
Examples
--------
>>> from colour import sRGB_COLOURSPACE as sRGB
>>> prng = np.random.RandomState(2)
>>> RGB_colourspace_visible_spectrum_coverage_MonteCarlo(
... sRGB,
... 10e3,
... random_state=prng) # doctest: +ELLIPSIS
36...
"""
return RGB_colourspace_volume_coverage_MonteCarlo(
colourspace,
is_within_visible_spectrum,
samples,
random_generator,
random_state)