Coverage for volume/rgb.py: 51%

1"""

2 RGB Colourspace Volume Computation

3==================================

4

5 Define RGB colourspace volume computation functionality.

6

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"""

13

14 from __future__ import annotations

15

16 import itertools

17 import typing

18

19 import numpy as np

20

21 from colour.algebra import random_triplet_generator

22 from colour.colorimetry import CCS_ILLUMINANTS

23 from colour.constants import DTYPE_INT_DEFAULT

24

25 if typing.TYPE_CHECKING:

26 from colour.hints import (

27 ArrayLike,

28 Callable,

29 LiteralChromaticAdaptationTransform,

30 NDArrayFloat,

31 )

32

33 from colour.models import (

34 Lab_to_XYZ,

35 RGB_Colourspace,

36 RGB_to_XYZ,

37 XYZ_to_Lab,

38 XYZ_to_RGB,

39)

40 from colour.utilities import as_float_array, multiprocessing_pool

41 from colour.volume import is_within_pointer_gamut, is_within_visible_spectrum

42

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"

49

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]

58

59

60 def _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.

65

66 Parameters

67 ----------

68 arguments

69 Arguments to pass to the wrapped function.

70

71 Returns

72 -------

73 :class:`int`

74 Inside *RGB* colourspace volume sample count.

75 """

76

77 return sample_RGB_colourspace_volume_MonteCarlo(*arguments)

78

79

80 def 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.

96

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.

115

116 Returns

117 -------

118 :class:`int`

119 Within *RGB* colourspace volume sample count.

120

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/\

130 reproducibility-of-python-pseudo-random-numbers-across-systems-and-versions

131

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 """

140

141 random_state = random_state if random_state is not None else np.random.RandomState()

142

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)

152

153

154 def RGB_colourspace_limits(colourspace: RGB_Colourspace) -> NDArrayFloat:

155 """

156 Compute the specified *RGB* colourspace volume limits in

157 *CIE L\\*a\\*b\\** colourspace.

158

159 Parameters

160 ----------

161 colourspace

162 *RGB* colourspace to compute the volume of.

163

164 Returns

165 -------

166 :class:`numpy.ndarray`

167 *RGB* colourspace volume limits.

168

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.

177

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 """

186

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 )

196

197 limits = [(np.min(Lab[..., i]), np.max(Lab[..., i])) for i in np.arange(3)]

198

199 return np.array(limits)

200

201

202 def 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.

218

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.

238

239 Returns

240 -------

241 :class:`float`

242 *RGB* colourspace volume.

243

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/\

253 reproducibility-of-python-pseudo-random-numbers-across-systems-and-versions

254

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 """

266

267 import multiprocessing # noqa: PLC0415

268

269 processes = multiprocessing.cpu_count()

270 process_samples = DTYPE_INT_DEFAULT(np.round(samples / processes))

271

272 arguments = (

273 colourspace,

274 process_samples,

275 limits,

276 illuminant_Lab,

277 chromatic_adaptation_transform,

278 random_generator,

279 random_state,

280 )

281

282 with multiprocessing_pool() as pool:

283 results = pool.map(

284 _wrapper_RGB_colourspace_volume_MonteCarlo,

285 [arguments for _ in range(processes)],

286 )

287

288 Lab_volume = np.prod([np.sum(np.abs(x)) for x in as_float_array(limits)])

289

290 return Lab_volume * np.sum(results) / (process_samples * processes)

291

292

293 def 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.

303

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.

317

318 Returns

319 -------

320 :class:`float`

321 Percentage coverage of volume.

322

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 """

333

334 random_state = random_state if random_state is not None else np.random.RandomState()

335

336 XYZ = random_generator(DTYPE_INT_DEFAULT(samples), random_state=random_state)

337 XYZ_vs = XYZ[coverage_sampler(XYZ)]

338

339 RGB = XYZ_to_RGB(XYZ_vs, colourspace)

340

341 RGB_c = RGB[np.logical_and(np.min(RGB, axis=-1) >= 0, np.max(RGB, axis=-1) <= 1)]

342

343 return 100 * RGB_c.size / XYZ_vs.size

344

345

346 def 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.

355

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.

368

369 Returns

370 -------

371 :class:`float`

372 Percentage coverage of *Pointer's Gamut* volume.

373

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 """

383

384 return RGB_colourspace_volume_coverage_MonteCarlo(

385 colourspace,

386 is_within_pointer_gamut,

387 samples,

388 random_generator,

389 random_state,

390 )

391

392

393 def 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.

402

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.

414

415 Returns

416 -------

417 :class:`float`

418 Percentage coverage of visible spectrum volume.

419

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 """

429

430 return RGB_colourspace_volume_coverage_MonteCarlo(

431 colourspace,

432 is_within_visible_spectrum,

433 samples,

434 random_generator,

435 random_state,

436 )