Coverage for volume/rgb.py: 51%
49 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-16 22:49 +1300
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-16 22:49 +1300
1"""
2RGB Colourspace Volume Computation
3==================================
5Define RGB colourspace volume computation functionality.
7- :func:`colour.RGB_colourspace_limits`
8- :func:`colour.RGB_colourspace_volume_MonteCarlo`
9- :func:`colour.RGB_colourspace_volume_coverage_MonteCarlo`
10- :func:`colour.RGB_colourspace_pointer_gamut_coverage_MonteCarlo`
11- :func:`colour.RGB_colourspace_visible_spectrum_coverage_MonteCarlo`
12"""
14from __future__ import annotations
16import itertools
17import typing
19import numpy as np
21from colour.algebra import random_triplet_generator
22from colour.colorimetry import CCS_ILLUMINANTS
23from colour.constants import DTYPE_INT_DEFAULT
25if typing.TYPE_CHECKING:
26 from colour.hints import (
27 ArrayLike,
28 Callable,
29 LiteralChromaticAdaptationTransform,
30 NDArrayFloat,
31 )
33from colour.models import (
34 Lab_to_XYZ,
35 RGB_Colourspace,
36 RGB_to_XYZ,
37 XYZ_to_Lab,
38 XYZ_to_RGB,
39)
40from colour.utilities import as_float_array, multiprocessing_pool
41from colour.volume import is_within_pointer_gamut, is_within_visible_spectrum
43__author__ = "Colour Developers"
44__copyright__ = "Copyright 2013 Colour Developers"
45__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
46__maintainer__ = "Colour Developers"
47__email__ = "colour-developers@colour-science.org"
48__status__ = "Production"
50__all__ = [
51 "sample_RGB_colourspace_volume_MonteCarlo",
52 "RGB_colourspace_limits",
53 "RGB_colourspace_volume_MonteCarlo",
54 "RGB_colourspace_volume_coverage_MonteCarlo",
55 "RGB_colourspace_pointer_gamut_coverage_MonteCarlo",
56 "RGB_colourspace_visible_spectrum_coverage_MonteCarlo",
57]
60def _wrapper_RGB_colourspace_volume_MonteCarlo(arguments: tuple) -> int:
61 """
62 Wrap the
63 :func:`colour.volume.rgb.sample_RGB_colourspace_volume_MonteCarlo`
64 function for parallel processing with multiple arguments.
66 Parameters
67 ----------
68 arguments
69 Arguments to pass to the wrapped function.
71 Returns
72 -------
73 :class:`int`
74 Inside *RGB* colourspace volume sample count.
75 """
77 return sample_RGB_colourspace_volume_MonteCarlo(*arguments)
80def sample_RGB_colourspace_volume_MonteCarlo(
81 colourspace: RGB_Colourspace,
82 samples: int = 1000000,
83 limits: ArrayLike = ([0, 100], [-150, 150], [-150, 150]),
84 illuminant_Lab: ArrayLike = CCS_ILLUMINANTS["CIE 1931 2 Degree Standard Observer"][
85 "D65"
86 ],
87 chromatic_adaptation_transform: (
88 LiteralChromaticAdaptationTransform | str | None
89 ) = "CAT02",
90 random_generator: Callable = random_triplet_generator,
91 random_state: np.random.RandomState | None = None,
92) -> int:
93 """
94 Randomly sample the *CIE L\\*a\\*b\\** colourspace volume and return the
95 ratio of samples within the specified *RGB* colourspace volume.
97 Parameters
98 ----------
99 colourspace
100 *RGB* colourspace to compute the volume of.
101 samples
102 Sample count.
103 limits
104 *CIE L\\*a\\*b\\** colourspace volume.
105 illuminant_Lab
106 *CIE L\\*a\\*b\\** colourspace *illuminant* chromaticity coordinates.
107 chromatic_adaptation_transform
108 *Chromatic adaptation* transform.
109 random_generator
110 Random triplet generator providing the random samples within the
111 *CIE L\\*a\\*b\\** colourspace volume.
112 random_state
113 Mersenne Twister pseudo-random number generator to use in the random
114 number generator.
116 Returns
117 -------
118 :class:`int`
119 Within *RGB* colourspace volume sample count.
121 Notes
122 -----
123 - The doctest is assuming that :func:`np.random.RandomState`
124 definition will return the same sequence no matter which *OS* or
125 *Python* version is used. There is however no formal promise about
126 the *prng* sequence reproducibility of either *Python* or *Numpy*
127 implementations: Laurent. (2012). Reproducibility of python
128 pseudo-random numbers across systems and versions? Retrieved January
129 20, 2015, from http://stackoverflow.com/questions/8786084/\
130reproducibility-of-python-pseudo-random-numbers-across-systems-and-versions
132 Examples
133 --------
134 >>> from colour.models import RGB_COLOURSPACE_sRGB as sRGB
135 >>> prng = np.random.RandomState(2)
136 >>> sample_RGB_colourspace_volume_MonteCarlo(sRGB, 10e3, random_state=prng)
137 ... # doctest: +ELLIPSIS
138 9...
139 """
141 random_state = random_state if random_state is not None else np.random.RandomState()
143 Lab = random_generator(DTYPE_INT_DEFAULT(samples), limits, random_state)
144 RGB = XYZ_to_RGB(
145 Lab_to_XYZ(Lab, illuminant_Lab),
146 colourspace,
147 illuminant_Lab,
148 chromatic_adaptation_transform,
149 )
150 RGB_w = RGB[np.logical_and(np.min(RGB, axis=-1) >= 0, np.max(RGB, axis=-1) <= 1)]
151 return len(RGB_w)
154def RGB_colourspace_limits(colourspace: RGB_Colourspace) -> NDArrayFloat:
155 """
156 Compute the specified *RGB* colourspace volume limits in
157 *CIE L\\*a\\*b\\** colourspace.
159 Parameters
160 ----------
161 colourspace
162 *RGB* colourspace to compute the volume of.
164 Returns
165 -------
166 :class:`numpy.ndarray`
167 *RGB* colourspace volume limits.
169 Notes
170 -----
171 The limits are computed for the specified *RGB* colourspace illuminant.
172 This is important to account for if the intent is to compare various
173 *RGB* colourspaces together. In this instance, they must be
174 chromatically adapted to the same illuminant beforehand. See
175 :meth:`colour.RGB_Colourspace.chromatically_adapt` method for more
176 information.
178 Examples
179 --------
180 >>> from colour.models import RGB_COLOURSPACE_sRGB as sRGB
181 >>> RGB_colourspace_limits(sRGB) # doctest: +ELLIPSIS
182 array([[ 0. ..., 100. ...],
183 [ -86.182855 ..., 98.2563272...],
184 [-107.8503557..., 94.4894974...]])
185 """
187 Lab = np.array(
188 [
189 XYZ_to_Lab(
190 RGB_to_XYZ(combination, colourspace),
191 colourspace.whitepoint,
192 )
193 for combination in list(itertools.product([0, 1], repeat=3))
194 ]
195 )
197 limits = [(np.min(Lab[..., i]), np.max(Lab[..., i])) for i in np.arange(3)]
199 return np.array(limits)
202def RGB_colourspace_volume_MonteCarlo(
203 colourspace: RGB_Colourspace,
204 samples: int = 1000000,
205 limits: ArrayLike = ([0, 100], [-150, 150], [-150, 150]),
206 illuminant_Lab: ArrayLike = CCS_ILLUMINANTS["CIE 1931 2 Degree Standard Observer"][
207 "D65"
208 ],
209 chromatic_adaptation_transform: (
210 LiteralChromaticAdaptationTransform | str | None
211 ) = "CAT02",
212 random_generator: Callable = random_triplet_generator,
213 random_state: np.random.RandomState | None = None,
214) -> float:
215 """
216 Compute the specified *RGB* colourspace volume using the *Monte Carlo*
217 method with multiprocessing.
219 Parameters
220 ----------
221 colourspace
222 *RGB* colourspace to compute the volume of.
223 samples
224 Sample count.
225 limits
226 *CIE L\\*a\\*b\\** colourspace volume boundaries.
227 illuminant_Lab
228 *CIE L\\*a\\*b\\** colourspace *illuminant* chromaticity
229 coordinates.
230 chromatic_adaptation_transform
231 *Chromatic adaptation* method.
232 random_generator
233 Random triplet generator providing the random samples within the
234 *CIE L\\*a\\*b\\** colourspace volume.
235 random_state
236 Mersenne Twister pseudo-random number generator to use in the
237 random number generator.
239 Returns
240 -------
241 :class:`float`
242 *RGB* colourspace volume.
244 Notes
245 -----
246 - The doctest is assuming that :func:`np.random.RandomState`
247 definition will return the same sequence no matter which *OS* or
248 *Python* version is used. There is however no formal promise about
249 the *prng* sequence reproducibility of either *Python* or *Numpy*
250 implementations: Laurent. (2012). Reproducibility of python
251 pseudo-random numbers across systems and versions? Retrieved January
252 20, 2015, from http://stackoverflow.com/questions/8786084/\
253reproducibility-of-python-pseudo-random-numbers-across-systems-and-versions
255 Examples
256 --------
257 >>> from colour.models import RGB_COLOURSPACE_sRGB as sRGB
258 >>> from colour.utilities import disable_multiprocessing
259 >>> prng = np.random.RandomState(2)
260 >>> with disable_multiprocessing():
261 ... RGB_colourspace_volume_MonteCarlo(sRGB, 10e3, random_state=prng)
262 ... # doctest: +SKIP
263 ...
264 8...
265 """
267 import multiprocessing # noqa: PLC0415
269 processes = multiprocessing.cpu_count()
270 process_samples = DTYPE_INT_DEFAULT(np.round(samples / processes))
272 arguments = (
273 colourspace,
274 process_samples,
275 limits,
276 illuminant_Lab,
277 chromatic_adaptation_transform,
278 random_generator,
279 random_state,
280 )
282 with multiprocessing_pool() as pool:
283 results = pool.map(
284 _wrapper_RGB_colourspace_volume_MonteCarlo,
285 [arguments for _ in range(processes)],
286 )
288 Lab_volume = np.prod([np.sum(np.abs(x)) for x in as_float_array(limits)])
290 return Lab_volume * np.sum(results) / (process_samples * processes)
293def RGB_colourspace_volume_coverage_MonteCarlo(
294 colourspace: RGB_Colourspace,
295 coverage_sampler: Callable,
296 samples: int = 1000000,
297 random_generator: Callable = random_triplet_generator,
298 random_state: np.random.RandomState | None = None,
299) -> float:
300 """
301 Compute the specified *RGB* colourspace percentage coverage of an
302 arbitrary volume.
304 Parameters
305 ----------
306 colourspace
307 *RGB* colourspace to compute the volume coverage percentage.
308 coverage_sampler
309 Python object responsible for checking the volume coverage.
310 samples
311 Sample count.
312 random_generator
313 Random triplet generator providing the random samples.
314 random_state
315 Mersenne Twister pseudo-random number generator to use in the
316 random number generator.
318 Returns
319 -------
320 :class:`float`
321 Percentage coverage of volume.
323 Examples
324 --------
325 >>> from colour.models import RGB_COLOURSPACE_sRGB as sRGB
326 >>> prng = np.random.RandomState(2)
327 >>> RGB_colourspace_volume_coverage_MonteCarlo(
328 ... sRGB, is_within_pointer_gamut, 10e3, random_state=prng
329 ... )
330 ... # doctest: +ELLIPSIS
331 81...
332 """
334 random_state = random_state if random_state is not None else np.random.RandomState()
336 XYZ = random_generator(DTYPE_INT_DEFAULT(samples), random_state=random_state)
337 XYZ_vs = XYZ[coverage_sampler(XYZ)]
339 RGB = XYZ_to_RGB(XYZ_vs, colourspace)
341 RGB_c = RGB[np.logical_and(np.min(RGB, axis=-1) >= 0, np.max(RGB, axis=-1) <= 1)]
343 return 100 * RGB_c.size / XYZ_vs.size
346def RGB_colourspace_pointer_gamut_coverage_MonteCarlo(
347 colourspace: RGB_Colourspace,
348 samples: int = 1000000,
349 random_generator: Callable = random_triplet_generator,
350 random_state: np.random.RandomState | None = None,
351) -> float:
352 """
353 Compute the specified *RGB* colourspace percentage coverage of
354 *Pointer's Gamut* volume using the *Monte Carlo* method.
356 Parameters
357 ----------
358 colourspace
359 *RGB* colourspace to compute the *Pointer's Gamut* coverage
360 percentage.
361 samples
362 Sample count.
363 random_generator
364 Random triplet generator providing the random samples.
365 random_state
366 Mersenne Twister pseudo-random number generator to use in the
367 random number generator.
369 Returns
370 -------
371 :class:`float`
372 Percentage coverage of *Pointer's Gamut* volume.
374 Examples
375 --------
376 >>> from colour.models import RGB_COLOURSPACE_sRGB as sRGB
377 >>> prng = np.random.RandomState(2)
378 >>> RGB_colourspace_pointer_gamut_coverage_MonteCarlo(
379 ... sRGB, 10e3, random_state=prng
380 ... ) # doctest: +ELLIPSIS
381 81...
382 """
384 return RGB_colourspace_volume_coverage_MonteCarlo(
385 colourspace,
386 is_within_pointer_gamut,
387 samples,
388 random_generator,
389 random_state,
390 )
393def RGB_colourspace_visible_spectrum_coverage_MonteCarlo(
394 colourspace: RGB_Colourspace,
395 samples: int = 1000000,
396 random_generator: Callable = random_triplet_generator,
397 random_state: np.random.RandomState | None = None,
398) -> float:
399 """
400 Compute the specified *RGB* colourspace percentage coverage of the visible
401 spectrum volume using the *Monte Carlo* method.
403 Parameters
404 ----------
405 colourspace
406 *RGB* colourspace to compute the visible spectrum coverage percentage.
407 samples
408 Sample count.
409 random_generator
410 Random triplet generator providing the random samples.
411 random_state
412 Mersenne Twister pseudo-random number generator to use in the random
413 number generator.
415 Returns
416 -------
417 :class:`float`
418 Percentage coverage of visible spectrum volume.
420 Examples
421 --------
422 >>> from colour.models import RGB_COLOURSPACE_sRGB as sRGB
423 >>> prng = np.random.RandomState(2)
424 >>> RGB_colourspace_visible_spectrum_coverage_MonteCarlo(
425 ... sRGB, 10e3, random_state=prng
426 ... ) # doctest: +ELLIPSIS
427 46...
428 """
430 return RGB_colourspace_volume_coverage_MonteCarlo(
431 colourspace,
432 is_within_visible_spectrum,
433 samples,
434 random_generator,
435 random_state,
436 )