Coverage for volume/rgb.py: 51%

49 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-11-16 22:49 +1300

1""" 

2RGB Colourspace Volume Computation 

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

4 

5Define 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 

14from __future__ import annotations 

15 

16import itertools 

17import typing 

18 

19import numpy as np 

20 

21from colour.algebra import random_triplet_generator 

22from colour.colorimetry import CCS_ILLUMINANTS 

23from colour.constants import DTYPE_INT_DEFAULT 

24 

25if typing.TYPE_CHECKING: 

26 from colour.hints import ( 

27 ArrayLike, 

28 Callable, 

29 LiteralChromaticAdaptationTransform, 

30 NDArrayFloat, 

31 ) 

32 

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 

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 

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. 

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 

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. 

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

130reproducibility-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 

154def 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 

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. 

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

253reproducibility-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 

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. 

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 

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. 

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 

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. 

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 )