Coverage for models/rgb/rgb_colourspace.py: 72%

243 statements  

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

1""" 

2RGB Colourspace and Transformations 

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

4 

5Define the :class:`colour.RGB_Colourspace` class for the *RGB* colourspaces 

6datasets from :mod:`colour.models.datasets.aces_rgb`, etc... and the following 

7*RGB* colourspace transformations or helper definitions: 

8 

9- :func:`colour.XYZ_to_RGB` 

10- :func:`colour.RGB_to_XYZ` 

11- :func:`colour.matrix_RGB_to_RGB` 

12- :func:`colour.RGB_to_RGB` 

13 

14References 

15---------- 

16- :cite:`InternationalElectrotechnicalCommission1999a` : International 

17 Electrotechnical Commission. (1999). IEC 61966-2-1:1999 - Multimedia 

18 systems and equipment - Colour measurement and management - Part 2-1: 

19 Colour management - Default RGB colour space - sRGB (p. 51). 

20 https://webstore.iec.ch/publication/6169 

21- :cite:`Panasonic2014a` : Panasonic. (2014). VARICAM V-Log/V-Gamut (pp. 

22 1-7). 

23 http://pro-av.panasonic.net/en/varicam/common/pdf/VARICAM_V-Log_V-Gamut.pdf 

24""" 

25 

26from __future__ import annotations 

27 

28import typing 

29from copy import deepcopy 

30 

31import numpy as np 

32 

33from colour.adaptation import matrix_chromatic_adaptation_VonKries 

34from colour.algebra import vecmul 

35 

36if typing.TYPE_CHECKING: 

37 from colour.hints import ( 

38 Any, 

39 ArrayLike, 

40 Callable, 

41 Domain1, 

42 LiteralChromaticAdaptationTransform, 

43 LiteralRGBColourspace, 

44 NDArrayFloat, 

45 Range1, 

46 ) 

47 

48from colour.hints import cast 

49from colour.models import xy_to_xyY, xy_to_XYZ, xyY_to_XYZ 

50from colour.models.rgb import chromatically_adapted_primaries, normalised_primary_matrix 

51from colour.utilities import ( 

52 as_float_array, 

53 attest, 

54 domain_range_scale, 

55 filter_kwargs, 

56 from_range_1, 

57 multiline_repr, 

58 multiline_str, 

59 optional, 

60 to_domain_1, 

61 usage_warning, 

62 validate_method, 

63) 

64 

65__author__ = "Colour Developers" 

66__copyright__ = "Copyright 2013 Colour Developers" 

67__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause" 

68__maintainer__ = "Colour Developers" 

69__email__ = "colour-developers@colour-science.org" 

70__status__ = "Production" 

71 

72__all__ = [ 

73 "RGB_Colourspace", 

74 "XYZ_to_RGB", 

75 "RGB_to_XYZ", 

76 "matrix_RGB_to_RGB", 

77 "RGB_to_RGB", 

78] 

79 

80 

81class RGB_Colourspace: 

82 """ 

83 Implement support for *RGB* colourspace datasets from modules including 

84 :mod:`colour.models.datasets.aces_rgb`. 

85 

86 Colour science literature related to *RGB* colourspaces and encodings 

87 defines datasets using different degrees of precision or rounding. While 

88 instances where a whitepoint differs from its canonical agreed value are 

89 rare, normalised primary matrices are commonly rounded at different 

90 decimal places. This can yield large discrepancies in computations. 

91 

92 Such an occurrence is the *V-Gamut* colourspace white paper, which 

93 defines the *V-Gamut* to *ITU-R BT.709* conversion matrix as follows:: 

94 

95 [[ 1.806576 -0.695697 -0.110879] 

96 [-0.170090 1.305955 -0.135865] 

97 [-0.025206 -0.154468 1.179674]] 

98 

99 Computing this matrix using *ITU-R BT.709* colourspace derived 

100 normalised primary matrix yields:: 

101 

102 [[ 1.8065736 -0.6956981 -0.1108786] 

103 [-0.1700890 1.3059548 -0.1358648] 

104 [-0.0252057 -0.1544678 1.1796737]] 

105 

106 The latter matrix is almost equal to the former, however performing the 

107 same computation using *IEC 61966-2-1:1999* *sRGB* colourspace 

108 normalised primary matrix introduces severe disparities:: 

109 

110 [[ 1.8063853 -0.6956147 -0.1109453] 

111 [-0.1699311 1.3058387 -0.1358616] 

112 [-0.0251630 -0.1544899 1.1797117]] 

113 

114 To provide support for both literature-defined datasets and accurate 

115 computations enabling transformations without loss of precision, the 

116 :class:`colour.RGB_Colourspace` class provides two sets of transformation 

117 matrices: 

118 

119 - Instantiation transformation matrices 

120 - Derived transformation matrices 

121 

122 Upon instantiation, the :class:`colour.RGB_Colourspace` class stores the 

123 specified ``matrix_RGB_to_XYZ`` and ``matrix_XYZ_to_RGB`` arguments and 

124 also computes their derived counterparts using the ``primaries`` and 

125 ``whitepoint`` arguments. 

126 

127 Whether instantiation or derived matrices are used in subsequent 

128 computations depends on the 

129 :attr:`colour.RGB_Colourspace.use_derived_matrix_RGB_to_XYZ` and 

130 :attr:`colour.RGB_Colourspace.use_derived_matrix_XYZ_to_RGB` attribute 

131 values. 

132 

133 Parameters 

134 ---------- 

135 name 

136 *RGB* colourspace name. 

137 primaries 

138 *RGB* colourspace primaries. 

139 whitepoint 

140 *RGB* colourspace whitepoint. 

141 whitepoint_name 

142 *RGB* colourspace whitepoint name. 

143 matrix_RGB_to_XYZ 

144 Transformation matrix from colourspace to *CIE XYZ* tristimulus 

145 values. 

146 matrix_XYZ_to_RGB 

147 Transformation matrix from *CIE XYZ* tristimulus values to 

148 colourspace. 

149 cctf_encoding 

150 Encoding colour component transfer function (Encoding CCTF) / 

151 opto-electronic transfer function (OETF) that maps estimated 

152 tristimulus values in a scene to :math:`R'G'B'` video component 

153 signal value. 

154 cctf_decoding 

155 Decoding colour component transfer function (Decoding CCTF) / 

156 electro-optical transfer function (EOTF) that maps an 

157 :math:`R'G'B'` video component signal value to tristimulus values at 

158 the display. 

159 use_derived_matrix_RGB_to_XYZ 

160 Whether to use the instantiation time normalised primary matrix or to 

161 use a computed derived normalised primary matrix. 

162 use_derived_matrix_XYZ_to_RGB 

163 Whether to use the instantiation time inverse normalised primary 

164 matrix or to use a computed derived inverse normalised primary 

165 matrix. 

166 

167 Attributes 

168 ---------- 

169 - :attr:`~colour.RGB_Colourspace.name` 

170 - :attr:`~colour.RGB_Colourspace.primaries` 

171 - :attr:`~colour.RGB_Colourspace.whitepoint` 

172 - :attr:`~colour.RGB_Colourspace.whitepoint_name` 

173 - :attr:`~colour.RGB_Colourspace.matrix_RGB_to_XYZ` 

174 - :attr:`~colour.RGB_Colourspace.matrix_XYZ_to_RGB` 

175 - :attr:`~colour.RGB_Colourspace.cctf_encoding` 

176 - :attr:`~colour.RGB_Colourspace.cctf_decoding` 

177 - :attr:`~colour.RGB_Colourspace.use_derived_matrix_RGB_to_XYZ` 

178 - :attr:`~colour.RGB_Colourspace.use_derived_matrix_XYZ_to_RGB` 

179 

180 Methods 

181 ------- 

182 - :attr:`~colour.RGB_Colourspace.__init__` 

183 - :attr:`~colour.RGB_Colourspace.__str__` 

184 - :attr:`~colour.RGB_Colourspace.__repr__` 

185 - :attr:`~colour.RGB_Colourspace.use_derived_transformation_matrices` 

186 - :attr:`~colour.RGB_Colourspace.chromatically_adapt` 

187 - :attr:`~colour.RGB_Colourspace.copy` 

188 

189 Notes 

190 ----- 

191 - The normalised primary matrix defined by 

192 :attr:`colour.RGB_Colourspace.matrix_RGB_to_XYZ` property is treated 

193 as the prime matrix from which the inverse will be calculated as 

194 required by the internal derivation mechanism. This behaviour has 

195 been chosen in accordance with literature where commonly a *RGB* 

196 colourspace is defined by its normalised primary matrix as it is 

197 directly computed from the chosen primaries and whitepoint. 

198 

199 References 

200 ---------- 

201 :cite:`InternationalElectrotechnicalCommission1999a`, 

202 :cite:`Panasonic2014a` 

203 

204 Examples 

205 -------- 

206 >>> p = np.array([0.73470, 0.26530, 0.00000, 1.00000, 0.00010, -0.07700]) 

207 >>> whitepoint = np.array([0.32168, 0.33767]) 

208 >>> matrix_RGB_to_XYZ = np.identity(3) 

209 >>> matrix_XYZ_to_RGB = np.identity(3) 

210 >>> colourspace = RGB_Colourspace( 

211 ... "RGB Colourspace", 

212 ... p, 

213 ... whitepoint, 

214 ... "ACES", 

215 ... matrix_RGB_to_XYZ, 

216 ... matrix_XYZ_to_RGB, 

217 ... ) 

218 >>> colourspace.matrix_RGB_to_XYZ 

219 array([[ 1., 0., 0.], 

220 [ 0., 1., 0.], 

221 [ 0., 0., 1.]]) 

222 >>> colourspace.matrix_XYZ_to_RGB 

223 array([[ 1., 0., 0.], 

224 [ 0., 1., 0.], 

225 [ 0., 0., 1.]]) 

226 >>> colourspace.use_derived_transformation_matrices(True) 

227 >>> colourspace.matrix_RGB_to_XYZ # doctest: +ELLIPSIS 

228 array([[ 9.5255239...e-01, 0.0000000...e+00, 9.3678631...e-05], 

229 [ 3.4396645...e-01, 7.2816609...e-01, -7.2132546...e-02], 

230 [ 0.0000000...e+00, 0.0000000...e+00, 1.0088251...e+00]]) 

231 >>> colourspace.matrix_XYZ_to_RGB # doctest: +ELLIPSIS 

232 array([[ 1.0498110...e+00, 0.0000000...e+00, -9.7484540...e-05], 

233 [ -4.9590302...e-01, 1.3733130...e+00, 9.8240036...e-02], 

234 [ 0.0000000...e+00, 0.0000000...e+00, 9.9125201...e-01]]) 

235 >>> colourspace.use_derived_matrix_RGB_to_XYZ = False 

236 >>> colourspace.matrix_RGB_to_XYZ 

237 array([[ 1., 0., 0.], 

238 [ 0., 1., 0.], 

239 [ 0., 0., 1.]]) 

240 >>> colourspace.use_derived_matrix_XYZ_to_RGB = False 

241 >>> colourspace.matrix_XYZ_to_RGB 

242 array([[ 1., 0., 0.], 

243 [ 0., 1., 0.], 

244 [ 0., 0., 1.]]) 

245 """ 

246 

247 def __init__( 

248 self, 

249 name: str, 

250 primaries: ArrayLike, 

251 whitepoint: ArrayLike, 

252 whitepoint_name: str | None = None, 

253 matrix_RGB_to_XYZ: ArrayLike | None = None, 

254 matrix_XYZ_to_RGB: ArrayLike | None = None, 

255 cctf_encoding: Callable | None = None, 

256 cctf_decoding: Callable | None = None, 

257 use_derived_matrix_RGB_to_XYZ: bool = False, 

258 use_derived_matrix_XYZ_to_RGB: bool = False, 

259 ) -> None: 

260 self._derived_matrix_RGB_to_XYZ: NDArrayFloat = np.array([]) 

261 self._derived_matrix_XYZ_to_RGB: NDArrayFloat = np.array([]) 

262 

263 self._name: str = f"{self.__class__.__name__} ({id(self)})" 

264 self.name = name 

265 self._primaries: NDArrayFloat = np.array([]) 

266 self.primaries = primaries 

267 self._whitepoint: NDArrayFloat = np.array([]) 

268 self.whitepoint = whitepoint 

269 self._whitepoint_name: str | None = None 

270 self.whitepoint_name = whitepoint_name 

271 self._matrix_RGB_to_XYZ: NDArrayFloat | None = None 

272 self.matrix_RGB_to_XYZ = matrix_RGB_to_XYZ 

273 self._matrix_XYZ_to_RGB: NDArrayFloat | None = None 

274 self.matrix_XYZ_to_RGB = matrix_XYZ_to_RGB 

275 self._cctf_encoding: Callable | None = None 

276 self.cctf_encoding = cctf_encoding 

277 self._cctf_decoding: Callable | None = None 

278 self.cctf_decoding = cctf_decoding 

279 self._use_derived_matrix_RGB_to_XYZ: bool = False 

280 self.use_derived_matrix_RGB_to_XYZ = use_derived_matrix_RGB_to_XYZ 

281 self._use_derived_matrix_XYZ_to_RGB: bool = False 

282 self.use_derived_matrix_XYZ_to_RGB = use_derived_matrix_XYZ_to_RGB 

283 

284 @property 

285 def name(self) -> str: 

286 """ 

287 Getter and setter for the *RGB* colourspace name. 

288 

289 Parameters 

290 ---------- 

291 value 

292 Value to set the name with. 

293 

294 Returns 

295 ------- 

296 :class:`str` 

297 *RGB* colourspace name. 

298 """ 

299 

300 return self._name 

301 

302 @name.setter 

303 def name(self, value: str) -> None: 

304 """Setter for the **self.name** property.""" 

305 

306 attest( 

307 isinstance(value, str), 

308 f'"name" property: "{value}" type is not "str"!', 

309 ) 

310 

311 self._name = value 

312 

313 @property 

314 def primaries(self) -> NDArrayFloat: 

315 """ 

316 Getter and setter for the *RGB* colourspace primaries. 

317 

318 Parameters 

319 ---------- 

320 value 

321 Value to set the primaries with. 

322 

323 Returns 

324 ------- 

325 :class:`numpy.ndarray` 

326 *RGB* colourspace primaries. 

327 """ 

328 

329 return self._primaries 

330 

331 @primaries.setter 

332 def primaries(self, value: ArrayLike) -> None: 

333 """Setter for the **self.primaries** property.""" 

334 

335 attest( 

336 isinstance(value, (tuple, list, np.ndarray, np.matrix)), 

337 f'"primaries" property: "{value!r}" is not a "tuple", ' 

338 f'"list", "ndarray" or "matrix" instance!', 

339 ) 

340 

341 value = as_float_array(value) 

342 

343 value = np.reshape(value, (3, 2)) 

344 

345 self._primaries = value 

346 

347 self._derived_matrix_XYZ_to_RGB = np.array([]) 

348 self._derived_matrix_RGB_to_XYZ = np.array([]) 

349 

350 @property 

351 def whitepoint(self) -> NDArrayFloat: 

352 """ 

353 Getter and setter for the *RGB* colourspace whitepoint. 

354 

355 Parameters 

356 ---------- 

357 value 

358 Value to set the whitepoint with. 

359 

360 Returns 

361 ------- 

362 :class:`numpy.ndarray` 

363 *RGB* colourspace whitepoint. 

364 """ 

365 

366 return self._whitepoint 

367 

368 @whitepoint.setter 

369 def whitepoint(self, value: ArrayLike) -> None: 

370 """Setter for the **self.whitepoint** property.""" 

371 

372 attest( 

373 isinstance(value, (tuple, list, np.ndarray, np.matrix)), 

374 f'"whitepoint" property: "{value!r}" is not a "tuple", ' 

375 f'"list", "ndarray" or "matrix" instance!', 

376 ) 

377 

378 value = as_float_array(value) 

379 

380 self._whitepoint = value 

381 self._derived_matrix_XYZ_to_RGB = np.array([]) 

382 self._derived_matrix_RGB_to_XYZ = np.array([]) 

383 

384 @property 

385 def whitepoint_name(self) -> str | None: 

386 """ 

387 Getter and setter for the *RGB* colourspace whitepoint name. 

388 

389 Define or retrieve the name identifier for the reference illuminant 

390 (whitepoint) used by this *RGB* colourspace. This property allows 

391 tracking of standardized illuminant names such as 'D65', 'D50', or 

392 custom whitepoint identifiers. 

393 

394 Parameters 

395 ---------- 

396 value 

397 Name identifier to set for the *RGB* colourspace whitepoint. Can 

398 be a standard illuminant name or custom identifier. 

399 

400 Returns 

401 ------- 

402 :class:`str` or :py:data:`None` 

403 *RGB* colourspace whitepoint name identifier. Returns 

404 :py:data:`None` if no name has been specified. 

405 """ 

406 

407 return self._whitepoint_name 

408 

409 @whitepoint_name.setter 

410 def whitepoint_name(self, value: str | None) -> None: 

411 """Setter for the **self.whitepoint_name** property.""" 

412 

413 if value is not None: 

414 attest( 

415 isinstance(value, str), 

416 f'"whitepoint_name" property: "{value}" type is not "str"!', 

417 ) 

418 

419 self._whitepoint_name = value 

420 

421 @property 

422 def matrix_RGB_to_XYZ(self) -> NDArrayFloat: 

423 """ 

424 Getter and setter for the transformation matrix from RGB colourspace 

425 to *CIE XYZ* tristimulus values. 

426 

427 Parameters 

428 ---------- 

429 value 

430 Transformation matrix from RGB colourspace to *CIE XYZ* 

431 tristimulus values. 

432 

433 Returns 

434 ------- 

435 :class:`numpy.ndarray` 

436 Transformation matrix from RGB colourspace to *CIE XYZ* 

437 tristimulus values. 

438 """ 

439 

440 if self._matrix_RGB_to_XYZ is None or self._use_derived_matrix_RGB_to_XYZ: 

441 if self._derived_matrix_RGB_to_XYZ.size == 0: 

442 self._derive_transformation_matrices() 

443 

444 return self._derived_matrix_RGB_to_XYZ 

445 

446 return self._matrix_RGB_to_XYZ 

447 

448 @matrix_RGB_to_XYZ.setter 

449 def matrix_RGB_to_XYZ(self, value: ArrayLike | None) -> None: 

450 """Setter for the **self.matrix_RGB_to_XYZ** property.""" 

451 

452 if value is not None: 

453 attest( 

454 isinstance(value, (tuple, list, np.ndarray, np.matrix)), 

455 f'"matrix_RGB_to_XYZ" property: "{value!r}" is not a "tuple", ' 

456 f'"list", "ndarray" or "matrix" instance!', 

457 ) 

458 

459 value = as_float_array(value) 

460 

461 self._matrix_RGB_to_XYZ = value 

462 

463 @property 

464 def matrix_XYZ_to_RGB(self) -> NDArrayFloat: 

465 """ 

466 Getter and setter for the transformation matrix from *CIE XYZ* 

467 tristimulus values to RGB colourspace. 

468 

469 Parameters 

470 ---------- 

471 value 

472 Transformation matrix from *CIE XYZ* tristimulus values to the 

473 colourspace. 

474 

475 Returns 

476 ------- 

477 :class:`numpy.ndarray` 

478 Transformation matrix from *CIE XYZ* tristimulus values to the 

479 colourspace. 

480 """ 

481 

482 if self._matrix_XYZ_to_RGB is None or self._use_derived_matrix_XYZ_to_RGB: 

483 if self._derived_matrix_XYZ_to_RGB.size == 0: 

484 self._derive_transformation_matrices() 

485 

486 return self._derived_matrix_XYZ_to_RGB 

487 

488 return self._matrix_XYZ_to_RGB 

489 

490 @matrix_XYZ_to_RGB.setter 

491 def matrix_XYZ_to_RGB(self, value: ArrayLike | None) -> None: 

492 """Setter for the **self.matrix_XYZ_to_RGB** property.""" 

493 

494 if value is not None: 

495 attest( 

496 isinstance(value, (tuple, list, np.ndarray, np.matrix)), 

497 f'"matrix_XYZ_to_RGB" property: "{value!r}" is not a "tuple", ' 

498 f'"list", "ndarray" or "matrix" instance!', 

499 ) 

500 

501 value = as_float_array(value) 

502 

503 self._matrix_XYZ_to_RGB = value 

504 

505 @property 

506 def cctf_encoding(self) -> Callable | None: 

507 """ 

508 Getter and setter for the encoding colour component transfer 

509 function (Encoding CCTF) / opto-electronic transfer function (OETF). 

510 

511 Parameters 

512 ---------- 

513 value 

514 Encoding colour component transfer function (Encoding CCTF) / 

515 opto-electronic transfer function (OETF). 

516 

517 Returns 

518 ------- 

519 Callable or :py:data:`None` 

520 Encoding colour component transfer function (Encoding CCTF) / 

521 opto-electronic transfer function (OETF). 

522 """ 

523 

524 return self._cctf_encoding 

525 

526 @cctf_encoding.setter 

527 def cctf_encoding(self, value: Callable | None) -> None: 

528 """Setter for the **self.cctf_encoding** property.""" 

529 

530 if value is not None: 

531 attest( 

532 callable(value), 

533 f'"cctf_encoding" property: "{value}" is not callable!', 

534 ) 

535 

536 self._cctf_encoding = value 

537 

538 @property 

539 def cctf_decoding(self) -> Callable | None: 

540 """ 

541 Getter and setter for the decoding colour component transfer 

542 function (Decoding CCTF) / electro-optical transfer function 

543 (EOTF). 

544 

545 Parameters 

546 ---------- 

547 value 

548 Decoding colour component transfer function (Decoding CCTF) / 

549 electro-optical transfer function (EOTF). 

550 

551 Returns 

552 ------- 

553 Callable or :py:data:`None` 

554 Decoding colour component transfer function (Decoding CCTF) / 

555 electro-optical transfer function (EOTF). 

556 """ 

557 

558 return self._cctf_decoding 

559 

560 @cctf_decoding.setter 

561 def cctf_decoding(self, value: Callable | None) -> None: 

562 """Setter for the **self.cctf_decoding** property.""" 

563 

564 if value is not None: 

565 attest( 

566 callable(value), 

567 f'"cctf_decoding" property: "{value}" is not callable!', 

568 ) 

569 

570 self._cctf_decoding = value 

571 

572 @property 

573 def use_derived_matrix_RGB_to_XYZ(self) -> bool: 

574 """ 

575 Getter and setter for whether to use the instantiation time normalised 

576 primary matrix or to use a computed derived normalised primary matrix. 

577 

578 Control whether the RGB to XYZ transformation uses the pre-computed 

579 normalised primary matrix from instantiation or derives it dynamically 

580 from the current primaries and whitepoint. 

581 

582 Parameters 

583 ---------- 

584 value 

585 Whether to use the instantiation time normalised primary matrix or 

586 to use a computed derived normalised primary matrix. 

587 

588 Returns 

589 ------- 

590 :class:`bool` 

591 Whether to use the instantiation time normalised primary matrix or 

592 to use a computed derived normalised primary matrix. 

593 """ 

594 

595 return self._use_derived_matrix_RGB_to_XYZ 

596 

597 @use_derived_matrix_RGB_to_XYZ.setter 

598 def use_derived_matrix_RGB_to_XYZ(self, value: bool) -> None: 

599 """Setter for the **self.use_derived_matrix_RGB_to_XYZ** property.""" 

600 

601 attest( 

602 isinstance(value, (bool, np.bool_)), 

603 f'"use_derived_matrix_RGB_to_XYZ" property: "{value}" is not a "bool"!', 

604 ) 

605 

606 self._use_derived_matrix_RGB_to_XYZ = value 

607 

608 @property 

609 def use_derived_matrix_XYZ_to_RGB(self) -> bool: 

610 """ 

611 Getter and setter for whether to use the instantiation time inverse 

612 normalised primary matrix or to compute a derived inverse normalised 

613 primary matrix. 

614 

615 Control whether the XYZ to RGB transformation uses the pre-computed 

616 inverse matrix from instantiation or derives it dynamically from the 

617 current primary matrix. 

618 

619 Parameters 

620 ---------- 

621 value 

622 Whether to use the instantiation time inverse normalised primary 

623 matrix or to compute a derived inverse normalised primary matrix. 

624 

625 Returns 

626 ------- 

627 :class:`bool` 

628 Whether to use the instantiation time inverse normalised primary 

629 matrix or to compute a derived inverse normalised primary matrix. 

630 """ 

631 

632 return self._use_derived_matrix_XYZ_to_RGB 

633 

634 @use_derived_matrix_XYZ_to_RGB.setter 

635 def use_derived_matrix_XYZ_to_RGB(self, value: bool) -> None: 

636 """Setter for the **self.use_derived_matrix_XYZ_to_RGB** property.""" 

637 

638 attest( 

639 isinstance(value, (bool, np.bool_)), 

640 f'"use_derived_matrix_XYZ_to_RGB" property: "{value}" is not a "bool"!', 

641 ) 

642 

643 self._use_derived_matrix_XYZ_to_RGB = value 

644 

645 def __str__(self) -> str: 

646 """ 

647 Generate a formatted string representation of the *RGB* colourspace. 

648 

649 Returns 

650 ------- 

651 :class:`str` 

652 Formatted string representation displaying colourspace properties 

653 including primaries, whitepoint, encoding/decoding CCTFs, and 

654 normalised primary matrices. 

655 

656 Examples 

657 -------- 

658 >>> p = np.array( 

659 ... [0.73470, 0.26530, 0.00000, 1.00000, 0.00010, -0.07700] 

660 ... ) 

661 >>> whitepoint = np.array([0.32168, 0.33767]) 

662 >>> matrix_RGB_to_XYZ = np.identity(3) 

663 >>> matrix_XYZ_to_RGB = np.identity(3) 

664 >>> cctf_encoding = lambda x: x 

665 >>> cctf_decoding = lambda x: x 

666 >>> print( 

667 ... RGB_Colourspace( 

668 ... "RGB Colourspace", 

669 ... p, 

670 ... whitepoint, 

671 ... "ACES", 

672 ... matrix_RGB_to_XYZ, 

673 ... matrix_XYZ_to_RGB, 

674 ... cctf_encoding, 

675 ... cctf_decoding, 

676 ... ) 

677 ... ) 

678 ... # doctest: +ELLIPSIS 

679 RGB Colourspace 

680 --------------- 

681 <BLANKLINE> 

682 Primaries : [[ 7.34700000e-01 2.65300000e-01] 

683 [ 0.00000000e+00 1.00000000e+00] 

684 [ 1.00000000e-04 -7.70000000e-02]] 

685 Whitepoint : [ 0.32168 0.33767] 

686 Whitepoint Name : ACES 

687 Encoding CCTF : <function <lambda> at 0x...> 

688 Decoding CCTF : <function <lambda> at 0x...> 

689 NPM : [[ 1. 0. 0.] 

690 [ 0. 1. 0.] 

691 [ 0. 0. 1.]] 

692 NPM -1 : [[ 1. 0. 0.] 

693 [ 0. 1. 0.] 

694 [ 0. 0. 1.]] 

695 Derived NPM : \ 

696[[ 9.5255239...e-01 0.0000000...e+00 9.3678631...e-05] 

697 \ 

698 [ 3.4396645...e-01 7.2816609...e-01 -7.2132546...e-02] 

699 \ 

700 [ 0.0000000...e+00 0.0000000...e+00 1.0088251...e+00]] 

701 Derived NPM -1 : \ 

702[[ 1.0498110...e+00 0.0000000...e+00 -9.7484540...e-05] 

703 \ 

704 [ -4.9590302...e-01 1.3733130...e+00 9.8240036...e-02] 

705 \ 

706 [ 0.0000000...e+00 0.0000000...e+00 9.9125201...e-01]] 

707 Use Derived NPM : False 

708 Use Derived NPM -1 : False 

709 """ 

710 if self._derived_matrix_XYZ_to_RGB.size == 0: 

711 self._derive_transformation_matrices() 

712 

713 return multiline_str( 

714 self, 

715 [ 

716 {"name": "name", "section": True}, 

717 {"line_break": True}, 

718 {"name": "primaries", "label": "Primaries"}, 

719 {"name": "whitepoint", "label": "Whitepoint"}, 

720 {"name": "whitepoint_name", "label": "Whitepoint Name"}, 

721 {"name": "cctf_encoding", "label": "Encoding CCTF"}, 

722 {"name": "cctf_decoding", "label": "Decoding CCTF"}, 

723 {"name": "_matrix_RGB_to_XYZ", "label": "NPM"}, 

724 {"name": "_matrix_XYZ_to_RGB", "label": "NPM -1"}, 

725 { 

726 "name": "_derived_matrix_RGB_to_XYZ", 

727 "label": "Derived NPM", 

728 }, 

729 { 

730 "name": "_derived_matrix_XYZ_to_RGB", 

731 "label": "Derived NPM -1", 

732 }, 

733 { 

734 "name": "use_derived_matrix_RGB_to_XYZ", 

735 "label": "Use Derived NPM", 

736 }, 

737 { 

738 "name": "use_derived_matrix_XYZ_to_RGB", 

739 "label": "Use Derived NPM -1", 

740 }, 

741 ], 

742 ) 

743 

744 def __repr__(self) -> str: 

745 """ 

746 Return an evaluable string representation of the *RGB* colourspace. 

747 

748 The representation includes all parameters needed to reconstruct the 

749 colourspace instance. 

750 

751 Returns 

752 ------- 

753 :class:`str` 

754 Evaluable string representation. 

755 

756 Examples 

757 -------- 

758 >>> from colour.models import linear_function 

759 >>> p = np.array([0.73470, 0.26530, 0.00000, 1.00000, 0.00010, -0.07700]) 

760 >>> whitepoint = np.array([0.32168, 0.33767]) 

761 >>> matrix_RGB_to_XYZ = np.identity(3) 

762 >>> matrix_XYZ_to_RGB = np.identity(3) 

763 >>> RGB_Colourspace( 

764 ... "RGB Colourspace", 

765 ... p, 

766 ... whitepoint, 

767 ... "ACES", 

768 ... matrix_RGB_to_XYZ, 

769 ... matrix_XYZ_to_RGB, 

770 ... linear_function, 

771 ... linear_function, 

772 ... ) 

773 ... # doctest: +ELLIPSIS 

774 RGB_Colourspace('RGB Colourspace', 

775 [[ 7.34700000e-01, 2.65300000e-01], 

776 [ 0.00000000e+00, 1.00000000e+00], 

777 [ 1.00000000e-04, -7.70000000e-02]], 

778 [ 0.32168, 0.33767], 

779 'ACES', 

780 [[ 1., 0., 0.], 

781 [ 0., 1., 0.], 

782 [ 0., 0., 1.]], 

783 [[ 1., 0., 0.], 

784 [ 0., 1., 0.], 

785 [ 0., 0., 1.]], 

786 linear_function, 

787 linear_function, 

788 False, 

789 False) 

790 """ 

791 

792 return multiline_repr( 

793 self, 

794 [ 

795 {"name": "name"}, 

796 {"name": "primaries"}, 

797 {"name": "whitepoint"}, 

798 {"name": "whitepoint_name"}, 

799 {"name": "matrix_RGB_to_XYZ"}, 

800 {"name": "matrix_XYZ_to_RGB"}, 

801 { 

802 "name": "cctf_encoding", 

803 "formatter": lambda x: ( # noqa: ARG005 

804 None 

805 if self.cctf_encoding is None 

806 else ( 

807 self.cctf_encoding.__name__ 

808 if hasattr(self.cctf_encoding, "__name__") 

809 else str(self.cctf_encoding) 

810 ) 

811 ), 

812 }, 

813 { 

814 "name": "cctf_decoding", 

815 "formatter": lambda x: ( # noqa: ARG005 

816 None 

817 if self.cctf_decoding is None 

818 else ( 

819 self.cctf_decoding.__name__ 

820 if hasattr(self.cctf_decoding, "__name__") 

821 else str(self.cctf_decoding) 

822 ) 

823 ), 

824 }, 

825 {"name": "use_derived_matrix_RGB_to_XYZ"}, 

826 {"name": "use_derived_matrix_XYZ_to_RGB"}, 

827 ], 

828 ) 

829 

830 def _derive_transformation_matrices(self) -> None: 

831 """ 

832 Derive transformation matrices from the RGB colourspace specification. 

833 

834 Compute the normalised primary matrix and its inverse matrix that are 

835 used for transformations between the RGB colourspace and CIE XYZ 

836 tristimulus values. 

837 """ 

838 

839 if self._primaries is not None and self._whitepoint is not None: 

840 npm = normalised_primary_matrix(self._primaries, self._whitepoint) 

841 

842 self._derived_matrix_RGB_to_XYZ = npm 

843 self._derived_matrix_XYZ_to_RGB = np.linalg.inv(npm) 

844 

845 def use_derived_transformation_matrices(self, usage: bool = True) -> None: 

846 """ 

847 Enable or disable usage of both derived transformation matrices, 

848 the normalised primary matrix and its inverse in subsequent 

849 computations. 

850 

851 Parameters 

852 ---------- 

853 usage 

854 Whether to use the derived transformation matrices. 

855 """ 

856 

857 self.use_derived_matrix_RGB_to_XYZ = usage 

858 self.use_derived_matrix_XYZ_to_RGB = usage 

859 

860 def chromatically_adapt( 

861 self, 

862 whitepoint: ArrayLike, 

863 whitepoint_name: str | None = None, 

864 chromatic_adaptation_transform: ( 

865 LiteralChromaticAdaptationTransform | str 

866 ) = "CAT02", 

867 ) -> RGB_Colourspace: 

868 """ 

869 Chromatically adapt the *RGB* colourspace *primaries* :math:`xy` 

870 chromaticity coordinates from *RGB* colourspace whitepoint to the 

871 specified reference whitepoint. 

872 

873 Parameters 

874 ---------- 

875 whitepoint 

876 Reference illuminant / whitepoint :math:`xy` chromaticity 

877 coordinates. 

878 whitepoint_name 

879 Reference illuminant / whitepoint name. 

880 chromatic_adaptation_transform 

881 *Chromatic adaptation* transform. 

882 

883 Returns 

884 ------- 

885 :class:`colour.RGB_Colourspace` 

886 Chromatically adapted *RGB* colourspace. 

887 

888 Examples 

889 -------- 

890 >>> p = np.array([0.73470, 0.26530, 0.00000, 1.00000, 0.00010, -0.07700]) 

891 >>> w_t = np.array([0.32168, 0.33767]) 

892 >>> w_r = np.array([0.31270, 0.32900]) 

893 >>> colourspace = RGB_Colourspace("RGB Colourspace", p, w_t, "D65") 

894 >>> print(colourspace.chromatically_adapt(w_r, "D50", "Bradford")) 

895 ... # doctest: +ELLIPSIS 

896 RGB Colourspace - Chromatically Adapted to 'D50' 

897 ------------------------------------------------ 

898 <BLANKLINE> 

899 Primaries : [[ 0.73485524 0.26422533] 

900 [-0.00617091 1.01131496] 

901 [ 0.01596756 -0.0642355 ]] 

902 Whitepoint : [ 0.3127 0.329 ] 

903 Whitepoint Name : D50 

904 Encoding CCTF : None 

905 Decoding CCTF : None 

906 NPM : None 

907 NPM -1 : None 

908 Derived NPM : [[ 0.93827985 -0.00445145 0.01662752] 

909 [ 0.33736889 0.72952157 -0.06689046] 

910 [ 0.00117395 -0.00371071 1.09159451]] 

911 Derived NPM -1 : [[ 1.06349549 0.00640891 -0.01580679] 

912 [-0.49207413 1.36822341 0.09133709] 

913 [-0.00281646 0.00464417 0.91641857]] 

914 Use Derived NPM : True 

915 Use Derived NPM -1 : True 

916 """ 

917 

918 colourspace = self.copy() 

919 

920 colourspace.primaries = chromatically_adapted_primaries( 

921 colourspace.primaries, 

922 colourspace.whitepoint, 

923 whitepoint, 

924 chromatic_adaptation_transform, 

925 ) 

926 colourspace.whitepoint = whitepoint 

927 colourspace.whitepoint_name = whitepoint_name 

928 

929 colourspace._matrix_RGB_to_XYZ = None # noqa: SLF001 

930 colourspace._matrix_XYZ_to_RGB = None # noqa: SLF001 

931 colourspace._derive_transformation_matrices() # noqa: SLF001 

932 colourspace.use_derived_transformation_matrices() 

933 

934 colourspace.name = ( 

935 f"{colourspace.name} - Chromatically Adapted to " 

936 f"{cast('str', optional(whitepoint_name, whitepoint))!r}" 

937 ) 

938 

939 return colourspace 

940 

941 def copy(self) -> RGB_Colourspace: 

942 """ 

943 Create a deep copy of the *RGB* colourspace instance. 

944 

945 Generate an independent copy of this *RGB* colourspace with all 

946 attributes duplicated, including primaries, whitepoint, matrices, 

947 and transfer functions. 

948 

949 Returns 

950 ------- 

951 :class:`colour.RGB_Colourspace` 

952 Independent deep copy of the *RGB* colourspace instance. 

953 """ 

954 

955 return deepcopy(self) 

956 

957 

958def XYZ_to_RGB( 

959 XYZ: Domain1, 

960 colourspace: RGB_Colourspace | LiteralRGBColourspace | str, 

961 illuminant: ArrayLike | None = None, 

962 chromatic_adaptation_transform: ( 

963 LiteralChromaticAdaptationTransform | str | None 

964 ) = "CAT02", 

965 apply_cctf_encoding: bool = False, 

966 *args: Any, 

967 **kwargs: Any, 

968) -> Range1: 

969 """ 

970 Convert from *CIE XYZ* tristimulus values to *RGB* colourspace array. 

971 

972 Parameters 

973 ---------- 

974 XYZ 

975 *CIE XYZ* tristimulus values. 

976 colourspace 

977 Output *RGB* colourspace. 

978 illuminant 

979 *CIE xy* chromaticity coordinates or *CIE xyY* colourspace array of 

980 the *illuminant* for the input *CIE XYZ* tristimulus values. 

981 chromatic_adaptation_transform 

982 *Chromatic adaptation* transform. If *None*, no chromatic adaptation 

983 is performed. 

984 apply_cctf_encoding 

985 Apply the *RGB* colourspace encoding colour component transfer 

986 function / opto-electronic transfer function. 

987 

988 Other Parameters 

989 ---------------- 

990 args 

991 Arguments for deprecation management. 

992 kwargs 

993 Keywords arguments for deprecation management. 

994 

995 Returns 

996 ------- 

997 :class:`numpy.ndarray` 

998 *RGB* colourspace array. 

999 

1000 Notes 

1001 ----- 

1002 +--------------------+-----------------------+---------------+ 

1003 | **Domain** | **Scale - Reference** | **Scale - 1** | 

1004 +====================+=======================+===============+ 

1005 | ``XYZ`` | 1 | 1 | 

1006 +--------------------+-----------------------+---------------+ 

1007 | ``illuminant_XYZ`` | 1 | 1 | 

1008 +--------------------+-----------------------+---------------+ 

1009 | ``illuminant_RGB`` | 1 | 1 | 

1010 +--------------------+-----------------------+---------------+ 

1011 

1012 +--------------------+-----------------------+---------------+ 

1013 | **Range** | **Scale - Reference** | **Scale - 1** | 

1014 +====================+=======================+===============+ 

1015 | ``RGB`` | 1 | 1 | 

1016 +--------------------+-----------------------+---------------+ 

1017 

1018 Examples 

1019 -------- 

1020 >>> from colour.models import RGB_COLOURSPACE_sRGB 

1021 >>> XYZ = np.array([0.21638819, 0.12570000, 0.03847493]) 

1022 >>> illuminant = np.array([0.34570, 0.35850]) 

1023 >>> XYZ_to_RGB(XYZ, RGB_COLOURSPACE_sRGB, illuminant, "Bradford") 

1024 ... # doctest: +ELLIPSIS 

1025 array([ 0.4559528..., 0.0304078..., 0.0408731...]) 

1026 >>> XYZ_to_RGB(XYZ, "sRGB", illuminant, "Bradford") 

1027 ... # doctest: +ELLIPSIS 

1028 array([ 0.4559528..., 0.0304078..., 0.0408731...]) 

1029 """ 

1030 

1031 from colour.models import RGB_COLOURSPACES # noqa: PLC0415 

1032 

1033 XYZ = to_domain_1(XYZ) 

1034 

1035 if not isinstance(colourspace, (RGB_Colourspace, str)): 

1036 usage_warning( 

1037 'The "colour.XYZ_to_RGB" definition signature has changed with ' 

1038 '"Colour 0.4.3". The used call arguments are deprecated, ' 

1039 "please refer to the documentation for more information about the " 

1040 "new signature." 

1041 ) 

1042 illuminant_XYZ = kwargs.pop("illuminant_XYZ", colourspace) 

1043 illuminant_RGB = kwargs.pop("illuminant_RGB", illuminant) 

1044 matrix_XYZ_to_RGB = kwargs.pop( 

1045 "matrix_XYZ_to_RGB", chromatic_adaptation_transform 

1046 ) 

1047 chromatic_adaptation_transform = kwargs.pop( 

1048 "chromatic_adaptation_transform", 

1049 ( 

1050 apply_cctf_encoding 

1051 if not isinstance(apply_cctf_encoding, bool) 

1052 else "CAT02" 

1053 ), 

1054 ) 

1055 cctf_encoding = kwargs.pop("cctf_encoding", args[0] if len(args) == 1 else None) 

1056 apply_cctf_encoding = True 

1057 else: 

1058 if isinstance(colourspace, str): 

1059 colourspace = validate_method( 

1060 colourspace, 

1061 tuple(RGB_COLOURSPACES), 

1062 '"{0}" "RGB" colourspace is invalid, it must be one of {1}!', 

1063 ) 

1064 colourspace = RGB_COLOURSPACES[colourspace] 

1065 

1066 illuminant_XYZ = optional( 

1067 illuminant, 

1068 colourspace.whitepoint, # pyright: ignore 

1069 ) 

1070 illuminant_RGB = colourspace.whitepoint # pyright: ignore 

1071 matrix_XYZ_to_RGB = colourspace.matrix_XYZ_to_RGB # pyright: ignore 

1072 cctf_encoding = colourspace.cctf_encoding # pyright: ignore 

1073 

1074 if chromatic_adaptation_transform is not None: 

1075 M_CAT = matrix_chromatic_adaptation_VonKries( 

1076 xyY_to_XYZ(xy_to_xyY(illuminant_XYZ)), 

1077 xyY_to_XYZ(xy_to_xyY(illuminant_RGB)), 

1078 transform=chromatic_adaptation_transform, 

1079 ) 

1080 

1081 XYZ = vecmul(M_CAT, XYZ) 

1082 

1083 RGB = vecmul(matrix_XYZ_to_RGB, XYZ) 

1084 

1085 if apply_cctf_encoding and cctf_encoding is not None: 

1086 with domain_range_scale("ignore"): 

1087 RGB = cctf_encoding(RGB) 

1088 

1089 return from_range_1(RGB) 

1090 

1091 

1092def RGB_to_XYZ( 

1093 RGB: Domain1, 

1094 colourspace: RGB_Colourspace | LiteralRGBColourspace | str, 

1095 illuminant: ArrayLike | None = None, 

1096 chromatic_adaptation_transform: ( 

1097 LiteralChromaticAdaptationTransform | str | None 

1098 ) = "CAT02", 

1099 apply_cctf_decoding: bool = False, 

1100 *args: Any, 

1101 **kwargs: Any, 

1102) -> Range1: 

1103 """ 

1104 Convert specified *RGB* colourspace array to *CIE XYZ* tristimulus values. 

1105 

1106 Parameters 

1107 ---------- 

1108 RGB 

1109 *RGB* colourspace array. 

1110 colourspace 

1111 Input *RGB* colourspace. 

1112 illuminant 

1113 *CIE xy* chromaticity coordinates or *CIE xyY* colourspace array of 

1114 the *illuminant* for the output *CIE XYZ* tristimulus values. 

1115 chromatic_adaptation_transform 

1116 *Chromatic adaptation* transform, if *None* no chromatic adaptation 

1117 is performed. 

1118 apply_cctf_decoding 

1119 Apply the *RGB* colourspace decoding colour component transfer 

1120 function / opto-electronic transfer function. 

1121 

1122 Other Parameters 

1123 ---------------- 

1124 args 

1125 Arguments for deprecation management. 

1126 kwargs 

1127 Keywords arguments for deprecation management. 

1128 

1129 Returns 

1130 ------- 

1131 :class:`numpy.ndarray` 

1132 *CIE XYZ* tristimulus values. 

1133 

1134 Notes 

1135 ----- 

1136 +--------------------+-----------------------+---------------+ 

1137 | **Domain** | **Scale - Reference** | **Scale - 1** | 

1138 +====================+=======================+===============+ 

1139 | ``RGB`` | 1 | 1 | 

1140 +--------------------+-----------------------+---------------+ 

1141 | ``illuminant_XYZ`` | 1 | 1 | 

1142 +--------------------+-----------------------+---------------+ 

1143 | ``illuminant_RGB`` | 1 | 1 | 

1144 +--------------------+-----------------------+---------------+ 

1145 

1146 +--------------------+-----------------------+---------------+ 

1147 | **Range** | **Scale - Reference** | **Scale - 1** | 

1148 +====================+=======================+===============+ 

1149 | ``XYZ`` | 1 | 1 | 

1150 +--------------------+-----------------------+---------------+ 

1151 

1152 Examples 

1153 -------- 

1154 >>> from colour.models import RGB_COLOURSPACE_sRGB 

1155 >>> RGB = np.array([0.45595571, 0.03039702, 0.04087245]) 

1156 >>> illuminant = np.array([0.34570, 0.35850]) 

1157 >>> RGB_to_XYZ(RGB, RGB_COLOURSPACE_sRGB, illuminant, "Bradford") 

1158 ... # doctest: +ELLIPSIS 

1159 array([ 0.2163881..., 0.1257 , 0.0384749...]) 

1160 >>> RGB_to_XYZ(RGB, "sRGB", illuminant, "Bradford") 

1161 ... # doctest: +ELLIPSIS 

1162 array([ 0.2163881..., 0.1257 , 0.0384749...]) 

1163 """ 

1164 

1165 from colour.models import RGB_COLOURSPACES # noqa: PLC0415 

1166 

1167 RGB = to_domain_1(RGB) 

1168 

1169 if not isinstance(colourspace, (RGB_Colourspace, str)): 

1170 usage_warning( 

1171 'The "colour.RGB_to_XYZ" definition signature has changed with ' 

1172 '"Colour 0.4.3". The used call arguments are deprecated, ' 

1173 "please refer to the documentation for more information about the " 

1174 "new signature." 

1175 ) 

1176 illuminant_RGB = kwargs.pop("illuminant_RGB", colourspace) 

1177 illuminant_XYZ = kwargs.pop("illuminant_XYZ", illuminant) 

1178 matrix_RGB_to_XYZ = kwargs.pop( 

1179 "matrix_RGB_to_XYZ", chromatic_adaptation_transform 

1180 ) 

1181 chromatic_adaptation_transform = kwargs.pop( 

1182 "chromatic_adaptation_transform", 

1183 ( 

1184 apply_cctf_decoding 

1185 if not isinstance(apply_cctf_decoding, bool) 

1186 else "CAT02" 

1187 ), 

1188 ) 

1189 cctf_decoding = kwargs.pop("cctf_decoding", args[0] if len(args) == 1 else None) 

1190 apply_cctf_decoding = True 

1191 else: 

1192 if isinstance(colourspace, str): 

1193 colourspace = validate_method( 

1194 colourspace, 

1195 tuple(RGB_COLOURSPACES), 

1196 '"{0}" "RGB" colourspace is invalid, it must be one of {1}!', 

1197 ) 

1198 colourspace = RGB_COLOURSPACES[colourspace] 

1199 

1200 illuminant_XYZ = optional( 

1201 illuminant, 

1202 colourspace.whitepoint, # pyright: ignore 

1203 ) 

1204 illuminant_RGB = colourspace.whitepoint # pyright: ignore 

1205 matrix_RGB_to_XYZ = colourspace.matrix_RGB_to_XYZ # pyright: ignore 

1206 cctf_decoding = colourspace.cctf_decoding # pyright: ignore 

1207 

1208 if apply_cctf_decoding and cctf_decoding is not None: 

1209 with domain_range_scale("ignore"): 

1210 RGB = cctf_decoding(RGB) 

1211 

1212 XYZ = vecmul(matrix_RGB_to_XYZ, RGB) 

1213 

1214 if chromatic_adaptation_transform is not None: 

1215 M_CAT = matrix_chromatic_adaptation_VonKries( 

1216 xyY_to_XYZ(xy_to_xyY(illuminant_RGB)), 

1217 xyY_to_XYZ(xy_to_xyY(illuminant_XYZ)), 

1218 transform=chromatic_adaptation_transform, 

1219 ) 

1220 

1221 XYZ = vecmul(M_CAT, XYZ) 

1222 

1223 return from_range_1(XYZ) 

1224 

1225 

1226def matrix_RGB_to_RGB( 

1227 input_colourspace: RGB_Colourspace | LiteralRGBColourspace | str, 

1228 output_colourspace: RGB_Colourspace | LiteralRGBColourspace | str, 

1229 chromatic_adaptation_transform: ( 

1230 LiteralChromaticAdaptationTransform | str | None 

1231 ) = "CAT02", 

1232) -> NDArrayFloat: 

1233 """ 

1234 Compute the matrix :math:`M` converting from the specified input *RGB* 

1235 colourspace to the specified output *RGB* colourspace using the 

1236 specified *chromatic adaptation* method. 

1237 

1238 Parameters 

1239 ---------- 

1240 input_colourspace 

1241 *RGB* input colourspace. 

1242 output_colourspace 

1243 *RGB* output colourspace. 

1244 chromatic_adaptation_transform 

1245 *Chromatic adaptation* transform. If *None*, no chromatic 

1246 adaptation is performed. 

1247 

1248 Returns 

1249 ------- 

1250 :class:`numpy.ndarray` 

1251 Conversion matrix :math:`M`. 

1252 

1253 Examples 

1254 -------- 

1255 >>> from colour.models import ( 

1256 ... RGB_COLOURSPACE_sRGB, 

1257 ... RGB_COLOURSPACE_PROPHOTO_RGB, 

1258 ... ) 

1259 >>> matrix_RGB_to_RGB(RGB_COLOURSPACE_sRGB, RGB_COLOURSPACE_PROPHOTO_RGB) 

1260 ... # doctest: +ELLIPSIS 

1261 array([[ 0.5288241..., 0.3340609..., 0.1373616...], 

1262 [ 0.0975294..., 0.8790074..., 0.0233981...], 

1263 [ 0.0163599..., 0.1066124..., 0.8772485...]]) 

1264 >>> matrix_RGB_to_RGB("sRGB", "ProPhoto RGB") 

1265 ... # doctest: +ELLIPSIS 

1266 array([[ 0.5288241..., 0.3340609..., 0.1373616...], 

1267 [ 0.0975294..., 0.8790074..., 0.0233981...], 

1268 [ 0.0163599..., 0.1066124..., 0.8772485...]]) 

1269 """ 

1270 

1271 from colour.models import RGB_COLOURSPACES # noqa: PLC0415 

1272 

1273 if isinstance(input_colourspace, str): 

1274 input_colourspace = validate_method( 

1275 input_colourspace, 

1276 tuple(RGB_COLOURSPACES), 

1277 '"{0}" "RGB" colourspace is invalid, it must be one of {1}!', 

1278 ) 

1279 input_colourspace = cast("RGB_Colourspace", RGB_COLOURSPACES[input_colourspace]) 

1280 

1281 if isinstance(output_colourspace, str): 

1282 output_colourspace = validate_method( 

1283 output_colourspace, 

1284 tuple(RGB_COLOURSPACES), 

1285 '"{0}" "RGB" colourspace is invalid, it must be one of {1}!', 

1286 ) 

1287 output_colourspace = cast( 

1288 "RGB_Colourspace", RGB_COLOURSPACES[output_colourspace] 

1289 ) 

1290 

1291 M = input_colourspace.matrix_RGB_to_XYZ 

1292 

1293 if chromatic_adaptation_transform is not None: 

1294 M_CAT = matrix_chromatic_adaptation_VonKries( 

1295 xy_to_XYZ(input_colourspace.whitepoint), 

1296 xy_to_XYZ(output_colourspace.whitepoint), 

1297 chromatic_adaptation_transform, 

1298 ) 

1299 

1300 M = np.matmul(M_CAT, input_colourspace.matrix_RGB_to_XYZ) 

1301 

1302 return np.matmul(output_colourspace.matrix_XYZ_to_RGB, M) 

1303 

1304 

1305def RGB_to_RGB( 

1306 RGB: Domain1, 

1307 input_colourspace: RGB_Colourspace | LiteralRGBColourspace | str, 

1308 output_colourspace: RGB_Colourspace | LiteralRGBColourspace | str, 

1309 chromatic_adaptation_transform: ( 

1310 LiteralChromaticAdaptationTransform | str | None 

1311 ) = "CAT02", 

1312 apply_cctf_decoding: bool = False, 

1313 apply_cctf_encoding: bool = False, 

1314 **kwargs: Any, 

1315) -> Range1: 

1316 """ 

1317 Convert *RGB* colourspace array from the specified input *RGB* colourspace to 

1318 specified output *RGB* colourspace using the specified *chromatic adaptation* 

1319 method. 

1320 

1321 Parameters 

1322 ---------- 

1323 RGB 

1324 *RGB* colourspace array. 

1325 input_colourspace 

1326 *RGB* input colourspace. 

1327 output_colourspace 

1328 *RGB* output colourspace. 

1329 chromatic_adaptation_transform 

1330 *Chromatic adaptation* transform, if *None* no chromatic adaptation 

1331 is performed. 

1332 apply_cctf_decoding 

1333 Apply the input colourspace decoding colour component transfer 

1334 function / electro-optical transfer function. 

1335 apply_cctf_encoding 

1336 Apply the output colourspace encoding colour component transfer 

1337 function / opto-electronic transfer function. 

1338 

1339 Other Parameters 

1340 ---------------- 

1341 kwargs 

1342 Keywords arguments for the colour component transfer functions. 

1343 

1344 Returns 

1345 ------- 

1346 :class:`numpy.ndarray` 

1347 *RGB* colourspace array. 

1348 

1349 Notes 

1350 ----- 

1351 +--------------------+-----------------------+---------------+ 

1352 | **Domain** | **Scale - Reference** | **Scale - 1** | 

1353 +====================+=======================+===============+ 

1354 | ``RGB`` | 1 | 1 | 

1355 +--------------------+-----------------------+---------------+ 

1356 

1357 +--------------------+-----------------------+---------------+ 

1358 | **Range** | **Scale - Reference** | **Scale - 1** | 

1359 +====================+=======================+===============+ 

1360 | ``RGB`` | 1 | 1 | 

1361 +--------------------+-----------------------+---------------+ 

1362 

1363 Examples 

1364 -------- 

1365 >>> from colour.models import ( 

1366 ... RGB_COLOURSPACE_sRGB, 

1367 ... RGB_COLOURSPACE_PROPHOTO_RGB, 

1368 ... ) 

1369 >>> RGB = np.array([0.45595571, 0.03039702, 0.04087245]) 

1370 >>> RGB_to_RGB(RGB, RGB_COLOURSPACE_sRGB, RGB_COLOURSPACE_PROPHOTO_RGB) 

1371 ... # doctest: +ELLIPSIS 

1372 array([ 0.2568891..., 0.0721446..., 0.0465553...]) 

1373 >>> RGB_to_RGB(RGB, "sRGB", "ProPhoto RGB") 

1374 ... # doctest: +ELLIPSIS 

1375 array([ 0.2568891..., 0.0721446..., 0.0465553...]) 

1376 """ 

1377 

1378 from colour.models import RGB_COLOURSPACES # noqa: PLC0415 

1379 

1380 if isinstance(input_colourspace, str): 

1381 input_colourspace = validate_method( 

1382 input_colourspace, 

1383 tuple(RGB_COLOURSPACES), 

1384 '"{0}" "RGB" colourspace is invalid, it must be one of {1}!', 

1385 ) 

1386 input_colourspace = cast("RGB_Colourspace", RGB_COLOURSPACES[input_colourspace]) 

1387 

1388 if isinstance(output_colourspace, str): 

1389 output_colourspace = validate_method( 

1390 output_colourspace, 

1391 tuple(RGB_COLOURSPACES), 

1392 '"{0}" "RGB" colourspace is invalid, it must be one of {1}!', 

1393 ) 

1394 output_colourspace = cast( 

1395 "RGB_Colourspace", RGB_COLOURSPACES[output_colourspace] 

1396 ) 

1397 

1398 RGB = to_domain_1(RGB) 

1399 

1400 if apply_cctf_decoding and input_colourspace.cctf_decoding is not None: 

1401 with domain_range_scale("ignore"): 

1402 RGB = input_colourspace.cctf_decoding( 

1403 RGB, **filter_kwargs(input_colourspace.cctf_decoding, **kwargs) 

1404 ) 

1405 

1406 M = matrix_RGB_to_RGB( 

1407 input_colourspace, output_colourspace, chromatic_adaptation_transform 

1408 ) 

1409 

1410 RGB = vecmul(M, RGB) 

1411 

1412 if apply_cctf_encoding and output_colourspace.cctf_encoding is not None: 

1413 with domain_range_scale("ignore"): 

1414 RGB = output_colourspace.cctf_encoding( 

1415 RGB, 

1416 **filter_kwargs(output_colourspace.cctf_encoding, **kwargs), 

1417 ) 

1418 

1419 return from_range_1(RGB)