Coverage for models/rgb/transfer_functions/log.py: 70%
63 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"""
2Common Log Encodings
3====================
5Define the common log encodings.
7- :func:`colour.models.logarithmic_function_basic`
8- :func:`colour.models.logarithmic_function_quasilog`
9- :func:`colour.models.logarithmic_function_camera`
10- :func:`colour.models.log_encoding_Log2`
11- :func:`colour.models.log_decoding_Log2`
13References
14----------
15- :cite:`TheAcademyofMotionPictureArtsandSciencesa` :
16 The Academy of Motion Picture Arts and Sciences,
17 Science and Technology Council,
18 & Academy Color Encoding System (ACES) Project Subcommittee.(n.d.).
19 ACESutil.Lin_to_Log2_param.ctl. Retrieved June 14, 2020,
20 from https://github.com/ampas/aces-dev/blob/\
21518c27f577e99cdecfddf2ebcfaa53444b1f9343/transforms/ctl/utilities/\
22ACESutil.Lin_to_Log2_param.ctl
23- :cite:`TheAcademyofMotionPictureArtsandSciencesb` :
24 The Academy of Motion Picture Arts and Sciences,
25 Science and Technology Council,
26 & Academy Color Encoding System (ACES) Project Subcommittee.(n.d.).
27 ACESutil.Log2_to_Lin_param.ctl. Retrieved June 14, 2020,
28 from https://github.com/ampas/aces-dev/blob/\
29518c27f577e99cdecfddf2ebcfaa53444b1f9343/transforms/ctl/utilities/\
30ACESutil.Log2_to_Lin_param.ctl
31: cite: `TheAcademyofMotionPictureArtsandSciences2020` : The Academy of
32 Motion Picture Arts and Sciences, Science and Technology Council, & Academy
33 Color Encoding System (ACES) Project Subcommittee. (2020). Specification
34 S-2014-006 - Common LUT Format (CLF) - A Common File Format for Look-Up
35 Tables. Retrieved June 24, 2020, from http://j.mp/S-2014-006
36"""
38from __future__ import annotations
40import typing
42import numpy as np
44from colour.algebra import sdiv, sdiv_mode
46if typing.TYPE_CHECKING:
47 from colour.hints import (
48 ArrayLike,
49 Literal,
50 NDArrayFloat,
51 )
53from colour.hints import cast
54from colour.utilities import (
55 as_float,
56 as_float_array,
57 optional,
58 validate_method,
59 zeros,
60)
62__author__ = "Colour Developers"
63__copyright__ = "Copyright 2013 Colour Developers"
64__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
65__maintainer__ = "Colour Developers"
66__email__ = "colour-developers@colour-science.org"
67__status__ = "Production"
69__all__ = [
70 "logarithmic_function_basic",
71 "logarithmic_function_quasilog",
72 "logarithmic_function_camera",
73 "log_encoding_Log2",
74 "log_decoding_Log2",
75]
77FLT_MIN = 1.175494e-38
80def logarithmic_function_basic(
81 x: ArrayLike,
82 style: (
83 Literal["log10", "antiLog10", "log2", "antiLog2", "logB", "antiLogB"] | str
84 ) = "log2",
85 base: int = 2,
86) -> NDArrayFloat:
87 """
88 Apply a logarithmic or anti-logarithmic transformation to the specified array.
90 Parameters
91 ----------
92 x
93 Logarithmically encoded data :math:`x`.
94 style
95 Specifies the behaviour for the logarithmic function to operate:
97 - *log10*: Applies a base 10 logarithm to the passed value.
98 - *antiLog10*: Applies a base 10 anti-logarithm to the passed value.
99 - *log2*: Applies a base 2 logarithm to the passed value.
100 - *antiLog2*: Applies a base 2 anti-logarithm to the passed value.
101 - *logB*: Applies an arbitrary base logarithm to the passed value.
102 - *antiLogB*: Applies an arbitrary base anti-logarithm to the passed
103 value.
104 base
105 Logarithmic base used for the conversion.
107 Returns
108 -------
109 :class:`numpy.ndarray`
110 Logarithmically transformed data.
112 Examples
113 --------
114 The basic logarithmic function *styles* operate as follows:
116 >>> logarithmic_function_basic(0.18) # doctest: +ELLIPSIS
117 -2.4739311...
118 >>> logarithmic_function_basic(0.18, "log10") # doctest: +ELLIPSIS
119 -0.7447274...
120 >>> logarithmic_function_basic(0.18, "logB", 3) # doctest: +ELLIPSIS
121 -1.5608767...
122 >>> logarithmic_function_basic( # doctest: +ELLIPSIS
123 ... -2.473931188332412, "antiLog2"
124 ... )
125 0.18000000...
126 >>> logarithmic_function_basic( # doctest: +ELLIPSIS
127 ... -0.7447274948966939, "antiLog10"
128 ... )
129 0.18000000...
130 >>> logarithmic_function_basic( # doctest: +ELLIPSIS
131 ... -1.5608767950073117, "antiLogB", 3
132 ... )
133 0.18000000...
134 """
136 x = as_float_array(x)
137 style = validate_method(
138 style,
139 ("log10", "antiLog10", "log2", "antiLog2", "logB", "antiLogB"),
140 '"{0}" style is invalid, it must be one of {1}!',
141 )
143 if style == "log10":
144 return as_float(np.where(x >= FLT_MIN, np.log10(x), np.log10(FLT_MIN)))
146 if style == "antilog10":
147 return as_float(10**x)
149 if style == "log2":
150 return as_float(np.where(x >= FLT_MIN, np.log2(x), np.log2(FLT_MIN)))
152 if style == "antilog2":
153 return as_float(2**x)
155 if style == "logb":
156 return as_float(np.log(x) / np.log(base))
158 # style == 'antilogb'
159 return as_float(base**x)
162def logarithmic_function_quasilog(
163 x: ArrayLike,
164 style: Literal["linToLog", "logToLin"] | str = "linToLog",
165 base: int = 2,
166 log_side_slope: float = 1,
167 lin_side_slope: float = 1,
168 log_side_offset: float = 0,
169 lin_side_offset: float = 0,
170) -> NDArrayFloat:
171 """
172 Apply the *Quasilog* logarithmic function for encoding and decoding.
174 This function implements a logarithmic transformation with configurable
175 slopes and offsets for both linear and logarithmic sides.
177 Parameters
178 ----------
179 x
180 Logarithmically encoded data :math:`x`.
181 style
182 Specifies the behaviour for the logarithmic function to operate:
184 - *linToLog*: Apply a logarithm to convert linear data to
185 logarithmic data.
186 - *logToLin*: Apply an anti-logarithm to convert logarithmic
187 data to linear data.
188 base
189 Logarithmic base used for the conversion.
190 log_side_slope
191 Slope (or gain) applied to the log side of the logarithmic function.
192 The default value is 1.
193 lin_side_slope
194 Slope of the linear side of the logarithmic function. The default
195 value is 1.
196 log_side_offset
197 Offset applied to the log side of the logarithmic function. The
198 default value is 0.
199 lin_side_offset
200 Offset applied to the linear side of the logarithmic function. The
201 default value is 0.
203 Returns
204 -------
205 :class:`numpy.ndarray`
206 Encoded/Decoded data.
208 Examples
209 --------
210 >>> logarithmic_function_quasilog(0.18, "linToLog") # doctest: +ELLIPSIS
211 -2.4739311...
212 >>> logarithmic_function_quasilog( # doctest: +ELLIPSIS
213 ... -2.473931188332412, "logToLin"
214 ... )
215 0.18000000...
216 """
218 x = as_float_array(x)
219 style = validate_method(
220 style,
221 ("lintolog", "logtolin"),
222 '"{0}" style is invalid, it must be one of {1}!',
223 )
225 if style == "lintolog":
226 y = (
227 log_side_slope
228 * (
229 np.log(np.maximum(lin_side_slope * x + lin_side_offset, FLT_MIN))
230 / np.log(base)
231 )
232 + log_side_offset
233 )
234 else: # style == 'logtolin'
235 with sdiv_mode():
236 y = sdiv(
237 base ** sdiv(x - log_side_offset, log_side_slope) - lin_side_offset,
238 lin_side_slope,
239 )
241 return as_float(y)
244def logarithmic_function_camera(
245 x: ArrayLike,
246 style: (Literal["cameraLinToLog", "cameraLogToLin"] | str) = "cameraLinToLog",
247 base: int = 2,
248 log_side_slope: float = 1,
249 lin_side_slope: float = 1,
250 log_side_offset: float = 0,
251 lin_side_offset: float = 0,
252 lin_side_break: float = 0.005,
253 linear_slope: float | None = None,
254) -> NDArrayFloat:
255 """
256 Apply a camera logarithmic function to the specified array.
258 Apply a piece-wise function with logarithmic and linear segments
259 for encoding or decoding camera data.
261 Parameters
262 ----------
263 x
264 Logarithmically encoded data :math:`x`.
265 style
266 Specifies the behaviour for the logarithmic function to operate:
268 - *cameraLinToLog*: Applies a piece-wise function with logarithmic
269 and linear segments on linear values, converting them to non-linear
270 values.
271 - *cameraLogToLin*: Applies a piece-wise function with logarithmic
272 and linear segments on non-linear values, converting them to linear
273 values.
274 base
275 Logarithmic base used for the conversion.
276 log_side_slope
277 Slope (or gain) applied to the log side of the logarithmic function.
278 The default value is 1.
279 lin_side_slope
280 Slope of the linear side of the logarithmic function. The
281 default value is 1.
282 log_side_offset
283 Offset applied to the log side of the logarithmic function. The
284 default value is 0.
285 lin_side_offset
286 Offset applied to the linear side of the logarithmic function.
287 The default value is 0.
288 lin_side_break
289 Break-point, defined in linear space, at which the piece-wise
290 function transitions between the logarithmic and linear
291 segments.
292 linear_slope
293 Slope of the linear portion of the curve. The default value is
294 *None*.
296 Returns
297 -------
298 :class:`numpy.ndarray`
299 Encoded/Decoded data.
301 Examples
302 --------
303 >>> logarithmic_function_camera( # doctest: +ELLIPSIS
304 ... 0.18, "cameraLinToLog"
305 ... )
306 -2.4739311...
307 >>> logarithmic_function_camera( # doctest: +ELLIPSIS
308 ... -2.4739311883324122, "cameraLogToLin"
309 ... )
310 0.1800000...
311 """
313 x = as_float_array(x)
314 style = validate_method(
315 style,
316 ("cameraLinToLog", "cameraLogToLin"),
317 '"{0}" style is invalid, it must be one of {1}!',
318 )
320 log_side_break = (
321 log_side_slope
322 * (np.log(lin_side_slope * lin_side_break + lin_side_offset) / np.log(base))
323 + log_side_offset
324 )
326 with sdiv_mode():
327 linear_slope = cast(
328 "float",
329 optional(
330 linear_slope,
331 (
332 log_side_slope
333 * (
334 sdiv(
335 lin_side_slope,
336 (lin_side_slope * lin_side_break + lin_side_offset)
337 * np.log(base),
338 )
339 )
340 ),
341 ),
342 )
344 linear_offset = log_side_break - linear_slope * lin_side_break
346 y = zeros(x.shape)
347 if style == "cameralintolog":
348 m_x = x <= lin_side_break
349 y[m_x] = linear_slope * x[m_x] + linear_offset
350 y[~m_x] = logarithmic_function_quasilog(
351 x[~m_x],
352 "linToLog",
353 base,
354 log_side_slope,
355 lin_side_slope,
356 log_side_offset,
357 lin_side_offset,
358 )
359 else: # style == 'cameralogtolin'
360 with sdiv_mode():
361 m_x = x <= log_side_break
362 y[m_x] = sdiv(x[m_x] - linear_offset, linear_slope)
363 y[~m_x] = logarithmic_function_quasilog(
364 x[~m_x],
365 "logToLin",
366 base,
367 log_side_slope,
368 lin_side_slope,
369 log_side_offset,
370 lin_side_offset,
371 )
373 return as_float(y)
376def log_encoding_Log2(
377 lin: ArrayLike,
378 middle_grey: float = 0.18,
379 min_exposure: float = -6.5,
380 max_exposure: float = 6.5,
381) -> NDArrayFloat:
382 """
383 Apply the common *Log2* log encoding opto-electronic transfer function (OETF).
385 Parameters
386 ----------
387 lin
388 Linear *Log2* decoded data.
389 middle_grey
390 *Middle Grey* exposure value.
391 min_exposure
392 Minimum exposure level.
393 max_exposure
394 Maximum exposure level.
396 Returns
397 -------
398 :class:`numpy.ndarray`
399 Non-linear *Log2* encoded data.
401 Notes
402 -----
403 +--------------+-----------------------+---------------+
404 | **Domain** | **Scale - Reference** | **Scale - 1** |
405 +==============+=======================+===============+
406 | ``lin`` | 1 | 1 |
407 +--------------+-----------------------+---------------+
409 +--------------+-----------------------+---------------+
410 | **Range** | **Scale - Reference** | **Scale - 1** |
411 +==============+=======================+===============+
412 | ``log_norm`` | 1 | 1 |
413 +--------------+-----------------------+---------------+
415 - The common *Log2* encoding function can be used to build linear to
416 logarithmic shapers in the *ACES OCIO configuration*.
417 - A (48-nits OCIO) shaper having values in a linear domain, can be
418 encoded to a logarithmic domain:
420 +-------------------+-------------------+
421 | **Shaper Domain** | **Shaper Range** |
422 +===================+===================+
423 | [0.002, 16.291] | [0, 1] |
424 +-------------------+-------------------+
426 References
427 ----------
428 :cite:`TheAcademyofMotionPictureArtsandSciencesa`
430 Examples
431 --------
432 >>> log_encoding_Log2(0.18)
433 0.5
434 """
436 lin = as_float_array(lin)
438 lg2 = np.log2(lin / middle_grey)
439 log_norm = (lg2 - min_exposure) / (max_exposure - min_exposure)
441 return as_float(log_norm)
444def log_decoding_Log2(
445 log_norm: ArrayLike,
446 middle_grey: float = 0.18,
447 min_exposure: float = -6.5,
448 max_exposure: float = 6.5,
449) -> NDArrayFloat:
450 """
451 Apply the common *Log2* log decoding inverse opto-electronic transfer
452 function (OETF).
454 Parameters
455 ----------
456 log_norm
457 Non-linear *Log2* encoded data.
458 middle_grey
459 *Middle Grey* exposure value.
460 min_exposure
461 Minimum exposure level.
462 max_exposure
463 Maximum exposure level.
465 Returns
466 -------
467 :class:`numpy.ndarray`
468 Linear *Log2* decoded data.
470 Notes
471 -----
472 +--------------+-----------------------+---------------+
473 | **Domain** | **Scale - Reference** | **Scale - 1** |
474 +==============+=======================+===============+
475 | ``log_norm`` | 1 | 1 |
476 +--------------+-----------------------+---------------+
478 +--------------+-----------------------+---------------+
479 | **Range** | **Scale - Reference** | **Scale - 1** |
480 +==============+=======================+===============+
481 | ``lin`` | 1 | 1 |
482 +--------------+-----------------------+---------------+
484 - The common *Log2* decoding function can be used to build logarithmic
485 to linear shapers in the *ACES OCIO configuration*.
486 - The shaper with logarithmic encoded values can be decoded back to
487 linear domain:
489 +-------------------+-------------------+
490 | **Shaper Range** | **Shaper Domain** |
491 +===================+===================+
492 | [0, 1] | [0.002, 16.291] |
493 +-------------------+-------------------+
495 References
496 ----------
497 :cite:`TheAcademyofMotionPictureArtsandSciencesb`
499 Examples
500 --------
501 >>> log_decoding_Log2(0.5) # doctest: +ELLIPSIS
502 0.1799999...
503 """
505 log_norm = as_float_array(log_norm)
507 lg2 = log_norm * (max_exposure - min_exposure) + min_exposure
508 lin = (2**lg2) * middle_grey
510 return as_float(lin)