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

1"""

2 RGB Colourspace and Transformations

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

4

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

6 datasets 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

14 References

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

26 from __future__ import annotations

27

28 import typing

29 from copy import deepcopy

30

31 import numpy as np

32

33 from colour.adaptation import matrix_chromatic_adaptation_VonKries

34 from colour.algebra import vecmul

35

36 if 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

48 from colour.hints import cast

49 from colour.models import xy_to_xyY, xy_to_XYZ, xyY_to_XYZ

50 from colour.models.rgb import chromatically_adapted_primaries, normalised_primary_matrix

51 from 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

81 class 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

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

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

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

1305 def 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)