Coverage for models/rgb/transfer_functions/rimm_romm_rgb.py: 67%
76 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-16 22:49 +1300
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-16 22:49 +1300
1"""
2RIMM, ROMM and ERIMM Encodings
3==============================
5Define the *RIMM, ROMM and ERIMM* encodings opto-electrical transfer functions
6(OETF) and electro-optical transfer functions (EOTF).
8- :func:`colour.models.cctf_encoding_ROMMRGB`
9- :func:`colour.models.cctf_decoding_ROMMRGB`
10- :func:`colour.models.cctf_encoding_ProPhotoRGB`
11- :func:`colour.models.cctf_decoding_ProPhotoRGB`
12- :func:`colour.models.cctf_encoding_RIMMRGB`
13- :func:`colour.models.cctf_decoding_RIMMRGB`
14- :func:`colour.models.log_encoding_ERIMMRGB`
15- :func:`colour.models.log_decoding_ERIMMRGB`
17References
18----------
19- :cite:`ANSI2003a` : ANSI. (2003). Specification of ROMM RGB (pp. 1-2).
20 http://www.color.org/ROMMRGB.pdf
21- :cite:`Spaulding2000b` : Spaulding, K. E., Woolfe, G. J., & Giorgianni, E.
22 J. (2000). Reference Input/Output Medium Metric RGB Color Encodings
23 (RIMM/ROMM RGB) (pp. 1-8). http://www.photo-lovers.org/pdf/color/romm.pdf
24"""
26from __future__ import annotations
28import typing
30import numpy as np
32from colour.algebra import spow
34if typing.TYPE_CHECKING:
35 from colour.hints import NDArrayReal
37from colour.hints import ( # noqa: TC001
38 Annotated,
39 Domain1,
40 Range1,
41)
42from colour.utilities import (
43 as_float,
44 as_float_scalar,
45 as_int,
46 copy_definition,
47 domain_range_scale,
48 from_range_1,
49 to_domain_1,
50)
52__author__ = "Colour Developers"
53__copyright__ = "Copyright 2013 Colour Developers"
54__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
55__maintainer__ = "Colour Developers"
56__email__ = "colour-developers@colour-science.org"
57__status__ = "Production"
59__all__ = [
60 "cctf_encoding_ROMMRGB",
61 "cctf_decoding_ROMMRGB",
62 "cctf_encoding_ProPhotoRGB",
63 "cctf_decoding_ProPhotoRGB",
64 "cctf_encoding_RIMMRGB",
65 "cctf_decoding_RIMMRGB",
66 "log_encoding_ERIMMRGB",
67 "log_decoding_ERIMMRGB",
68]
71def cctf_encoding_ROMMRGB(
72 X: Domain1, bit_depth: int = 8, out_int: bool = False
73) -> Annotated[NDArrayReal, 1]:
74 """
75 Apply the *ROMM RGB* encoding colour component transfer function
76 (Encoding CCTF).
78 Parameters
79 ----------
80 X
81 Linear data :math:`X_{ROMM}`.
82 bit_depth
83 Bit-depth used for conversion.
84 out_int
85 Whether to return value as integer code value or floating point
86 equivalent of a code value at a specified bit-depth.
88 Returns
89 -------
90 :class:`numpy.ndarray`
91 Non-linear encoded data :math:`X'_{ROMM}`.
93 Notes
94 -----
95 +------------+-----------------------+---------------+
96 | **Domain** | **Scale - Reference** | **Scale - 1** |
97 +============+=======================+===============+
98 | ``X`` | 1 | 1 |
99 +------------+-----------------------+---------------+
101 +------------+-----------------------+---------------+
102 | **Range** | **Scale - Reference** | **Scale - 1** |
103 +============+=======================+===============+
104 | ``X_p`` | 1 | 1 |
105 +------------+-----------------------+---------------+
107 - This definition has an output int switch, thus the domain-range
108 scale information is only specified for the floating point mode.
110 References
111 ----------
112 :cite:`ANSI2003a`, :cite:`Spaulding2000b`
114 Examples
115 --------
116 >>> cctf_encoding_ROMMRGB(0.18) # doctest: +ELLIPSIS
117 0.3857114...
118 >>> cctf_encoding_ROMMRGB(0.18, out_int=True)
119 98
120 """
122 X = to_domain_1(X)
124 I_max = 2**bit_depth - 1
126 E_t = 16 ** (1.8 / (1 - 1.8))
128 X_p = np.where(E_t > X, X * 16 * I_max, spow(X, 1 / 1.8) * I_max)
130 if out_int:
131 return as_int(np.round(X_p))
133 return as_float(from_range_1(X_p / I_max))
136def cctf_decoding_ROMMRGB(
137 X_p: Domain1,
138 bit_depth: int = 8,
139 in_int: bool = False,
140) -> Range1:
141 """
142 Apply the *ROMM RGB* decoding colour component transfer function
143 (Decoding CCTF).
145 Parameters
146 ----------
147 X_p
148 Non-linear encoded data :math:`X'_{ROMM}`.
149 bit_depth
150 Bit-depth used for conversion.
151 in_int
152 Whether to treat the input value as integer code value or floating
153 point equivalent of a code value at a specified bit-depth.
155 Returns
156 -------
157 :class:`numpy.ndarray`
158 Linear data :math:`X_{ROMM}`.
160 Notes
161 -----
162 +------------+-----------------------+---------------+
163 | **Domain** | **Scale - Reference** | **Scale - 1** |
164 +============+=======================+===============+
165 | ``X_p`` | 1 | 1 |
166 +------------+-----------------------+---------------+
168 +------------+-----------------------+---------------+
169 | **Range** | **Scale - Reference** | **Scale - 1** |
170 +============+=======================+===============+
171 | ``X`` | 1 | 1 |
172 +------------+-----------------------+---------------+
174 - This definition has an input int switch, thus the domain-range
175 scale information is only specified for the floating point mode.
177 References
178 ----------
179 :cite:`ANSI2003a`, :cite:`Spaulding2000b`
181 Examples
182 --------
183 >>> cctf_decoding_ROMMRGB(0.385711424751138) # doctest: +ELLIPSIS
184 0.1...
185 >>> cctf_decoding_ROMMRGB(98, in_int=True) # doctest: +ELLIPSIS
186 0.1...
187 """
189 X_p = to_domain_1(X_p)
191 I_max = 2**bit_depth - 1
193 if not in_int:
194 X_p = X_p * I_max
196 E_t = 16 ** (1.8 / (1 - 1.8))
198 X = np.where(
199 X_p < 16 * E_t * I_max,
200 X_p / (16 * I_max),
201 spow(X_p / I_max, 1.8),
202 )
204 return as_float(from_range_1(X))
207cctf_encoding_ProPhotoRGB = copy_definition(
208 cctf_encoding_ROMMRGB, "cctf_encoding_ProPhotoRGB"
209)
210# If-clause required for optimised python launch.
211if cctf_encoding_ProPhotoRGB.__doc__ is not None:
212 cctf_encoding_ProPhotoRGB.__doc__ = cctf_encoding_ProPhotoRGB.__doc__.replace(
213 "*ROMM RGB*", "*ProPhoto RGB*"
214 )
215cctf_decoding_ProPhotoRGB = copy_definition(
216 cctf_decoding_ROMMRGB, "cctf_decoding_ProPhotoRGB"
217)
218# If-clause required for optimised python launch.
219if cctf_decoding_ProPhotoRGB.__doc__ is not None:
220 cctf_decoding_ProPhotoRGB.__doc__ = cctf_decoding_ProPhotoRGB.__doc__.replace(
221 "*ROMM RGB*", "*ProPhoto RGB*"
222 )
225def cctf_encoding_RIMMRGB(
226 X: Domain1,
227 bit_depth: int = 8,
228 out_int: bool = False,
229 E_clip: float = 2.0,
230) -> Annotated[NDArrayReal, 1]:
231 """
232 Apply the *RIMM RGB* encoding colour component transfer function
233 (Encoding CCTF).
235 Parameters
236 ----------
237 X
238 Linear data :math:`X_{RIMM}`.
239 bit_depth
240 Bit-depth used for conversion.
241 out_int
242 Whether to return value as integer code value or floating point
243 equivalent of a code value at a specified bit-depth.
244 E_clip
245 Maximum exposure limit.
247 Returns
248 -------
249 :class:`numpy.ndarray`
250 Non-linear encoded data :math:`X'_{RIMM}`.
252 Notes
253 -----
254 +------------+-----------------------+---------------+
255 | **Domain** | **Scale - Reference** | **Scale - 1** |
256 +============+=======================+===============+
257 | ``X`` | 1 | 1 |
258 +------------+-----------------------+---------------+
260 +------------+-----------------------+---------------+
261 | **Range** | **Scale - Reference** | **Scale - 1** |
262 +============+=======================+===============+
263 | ``X_p`` | 1 | 1 |
264 +------------+-----------------------+---------------+
266 - This definition has an output int switch, thus the domain-range
267 scale information is only specified for the floating point mode.
269 References
270 ----------
271 :cite:`Spaulding2000b`
273 Examples
274 --------
275 >>> cctf_encoding_RIMMRGB(0.18) # doctest: +ELLIPSIS
276 0.2916737...
277 >>> cctf_encoding_RIMMRGB(0.18, out_int=True)
278 74
279 """
281 X = to_domain_1(X)
283 I_max = 2**bit_depth - 1
285 V_clip = 1.099 * spow(E_clip, 0.45) - 0.099
286 q = I_max / V_clip
288 X_p = q * np.select(
289 [X < 0.0, X < 0.018, X >= 0.018, E_clip < X],
290 [0, 4.5 * X, 1.099 * spow(X, 0.45) - 0.099, I_max],
291 )
293 if out_int:
294 return as_int(np.round(X_p))
296 return as_float(from_range_1(X_p / I_max))
299def cctf_decoding_RIMMRGB(
300 X_p: Domain1,
301 bit_depth: int = 8,
302 in_int: bool = False,
303 E_clip: float = 2.0,
304) -> Range1:
305 """
306 Apply the *RIMM RGB* decoding colour component transfer function
307 (Decoding CCTF).
309 Parameters
310 ----------
311 X_p
312 Non-linear encoded data :math:`X'_{RIMM}`.
313 bit_depth
314 Bit-depth used for conversion.
315 in_int
316 Whether to treat the input value as integer code value or floating
317 point equivalent of a code value at a specified bit-depth.
318 E_clip
319 Maximum exposure limit.
321 Returns
322 -------
323 :class:`numpy.ndarray`
324 Linear data :math:`X_{RIMM}`.
326 Notes
327 -----
328 +------------+-----------------------+---------------+
329 | **Domain** | **Scale - Reference** | **Scale - 1** |
330 +============+=======================+===============+
331 | ``X_p`` | 1 | 1 |
332 +------------+-----------------------+---------------+
334 +------------+-----------------------+---------------+
335 | **Range** | **Scale - Reference** | **Scale - 1** |
336 +============+=======================+===============+
337 | ``X`` | 1 | 1 |
338 +------------+-----------------------+---------------+
340 - This definition has an input int switch, thus the domain-range
341 scale information is only specified for the floating point mode.
343 References
344 ----------
345 :cite:`Spaulding2000b`
347 Examples
348 --------
349 >>> cctf_decoding_RIMMRGB(0.291673732475746) # doctest: +ELLIPSIS
350 0.1...
351 >>> cctf_decoding_RIMMRGB(74, in_int=True) # doctest: +ELLIPSIS
352 0.1...
353 """
355 X_p = to_domain_1(X_p)
357 I_max = as_float_scalar(2**bit_depth - 1)
359 if not in_int:
360 X_p = X_p * I_max
362 V_clip = 1.099 * spow(E_clip, 0.45) - 0.099
364 m = V_clip * X_p / I_max
366 with domain_range_scale("ignore"):
367 X = np.where(
368 X_p / I_max < cctf_encoding_RIMMRGB(0.018, bit_depth, E_clip=E_clip),
369 m / 4.5,
370 spow((m + 0.099) / 1.099, 1 / 0.45),
371 )
373 return as_float(from_range_1(X))
376def log_encoding_ERIMMRGB(
377 X: Domain1,
378 bit_depth: int = 8,
379 out_int: bool = False,
380 E_min: float = 0.001,
381 E_clip: float = 316.2,
382) -> Annotated[NDArrayReal, 1]:
383 """
384 Apply the *ERIMM RGB* log encoding opto-electronic transfer function (OETF).
386 Parameters
387 ----------
388 X
389 Linear data :math:`X_{ERIMM}`.
390 bit_depth
391 Bit-depth used for conversion.
392 out_int
393 Whether to return value as integer code value or floating point
394 equivalent of a code value at a specified bit-depth.
395 E_min
396 Minimum exposure limit.
397 E_clip
398 Maximum exposure limit.
400 Returns
401 -------
402 :class:`numpy.ndarray`
403 Non-linear encoded data :math:`X'_{ERIMM}`.
405 Notes
406 -----
407 +------------+-----------------------+---------------+
408 | **Domain** | **Scale - Reference** | **Scale - 1** |
409 +============+=======================+===============+
410 | ``X`` | 1 | 1 |
411 +------------+-----------------------+---------------+
413 +------------+-----------------------+---------------+
414 | **Range** | **Scale - Reference** | **Scale - 1** |
415 +============+=======================+===============+
416 | ``X_p`` | 1 | 1 |
417 +------------+-----------------------+---------------+
419 - This definition has an output int switch, thus the domain-range
420 scale information is only specified for the floating point mode.
422 References
423 ----------
424 :cite:`Spaulding2000b`
426 Examples
427 --------
428 >>> log_encoding_ERIMMRGB(0.18) # doctest: +ELLIPSIS
429 0.4100523...
430 >>> log_encoding_ERIMMRGB(0.18, out_int=True)
431 105
432 """
434 X = to_domain_1(X)
436 I_max = 2**bit_depth - 1
438 E_t = np.exp(1) * E_min
440 l_E_t = np.log(E_t)
441 l_E_min = np.log(E_min)
442 l_E_clip = np.log(E_clip)
443 X_p = np.select(
444 [
445 X < 0.0,
446 E_t >= X,
447 E_t < X,
448 E_clip < X,
449 ],
450 [
451 0,
452 I_max * ((l_E_t - l_E_min) / (l_E_clip - l_E_min)) * X / E_t,
453 I_max * ((np.log(X) - l_E_min) / (l_E_clip - l_E_min)),
454 I_max,
455 ],
456 )
458 if out_int:
459 return as_int(np.round(X_p))
461 return as_float(from_range_1(X_p / I_max))
464def log_decoding_ERIMMRGB(
465 X_p: Domain1,
466 bit_depth: int = 8,
467 in_int: bool = False,
468 E_min: float = 0.001,
469 E_clip: float = 316.2,
470) -> Range1:
471 """
472 Apply the *ERIMM RGB* log decoding inverse opto-electronic transfer function (OETF).
474 Parameters
475 ----------
476 X_p
477 Non-linear encoded data :math:`X'_{ERIMM}`.
478 bit_depth
479 Bit-depth used for conversion.
480 in_int
481 Whether to treat the input value as integer code value or floating
482 point equivalent of a code value at a specified bit-depth.
483 E_min
484 Minimum exposure limit.
485 E_clip
486 Maximum exposure limit.
488 Returns
489 -------
490 :class:`numpy.ndarray`
491 Linear data :math:`X_{ERIMM}`.
493 Notes
494 -----
495 +------------+-----------------------+---------------+
496 | **Domain** | **Scale - Reference** | **Scale - 1** |
497 +============+=======================+===============+
498 | ``X_p`` | 1 | 1 |
499 +------------+-----------------------+---------------+
501 +------------+-----------------------+---------------+
502 | **Range** | **Scale - Reference** | **Scale - 1** |
503 +============+=======================+===============+
504 | ``X`` | 1 | 1 |
505 +------------+-----------------------+---------------+
507 - This definition has an input int switch, thus the domain-range
508 scale information is only specified for the floating point mode.
510 References
511 ----------
512 :cite:`Spaulding2000b`
514 Examples
515 --------
516 >>> log_decoding_ERIMMRGB(0.410052389492129) # doctest: +ELLIPSIS
517 0.1...
518 >>> log_decoding_ERIMMRGB(105, in_int=True) # doctest: +ELLIPSIS
519 0.1...
520 """
522 X_p = to_domain_1(X_p)
524 I_max = 2**bit_depth - 1
526 if not in_int:
527 X_p = X_p * I_max
529 E_t = np.exp(1) * E_min
531 l_E_t = np.log(E_t)
532 l_E_min = np.log(E_min)
533 l_E_clip = np.log(E_clip)
534 X = np.where(
535 X_p <= I_max * ((l_E_t - l_E_min) / (l_E_clip - l_E_min)),
536 ((l_E_clip - l_E_min) / (l_E_t - l_E_min)) * ((X_p * E_t) / I_max),
537 np.exp((X_p / I_max) * (l_E_clip - l_E_min) + l_E_min),
538 )
540 return as_float(from_range_1(X))