"""
Basic and Monitor-Curve Exponent Transfer Functions
===================================================
Define the exponent transfer functions.
- :func:`colour.models.exponent_function_basic`
- :func:`colour.models.exponent_function_monitor_curve`
References
----------
- :cite: `TheAcademyofMotionPictureArtsandSciences2020` : The Academy of
Motion Picture Arts and Sciences, Science and Technology Council, & Academy
Color Encoding System (ACES) Project Subcommittee. (2020). Specification
S-2014-006 - Common LUT Format (CLF) - A Common File Format for Look-Up
Tables. Retrieved June 24, 2020, from http://j.mp/S-2014-006
"""
from __future__ import annotations
import typing
from colour.algebra import sdiv, sdiv_mode
if typing.TYPE_CHECKING:
from colour.hints import ArrayLike, Literal, NDArrayFloat
from colour.utilities import as_float, as_float_array, validate_method, zeros
__author__ = "Colour Developers"
__copyright__ = "Copyright 2013 Colour Developers"
__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
__maintainer__ = "Colour Developers"
__email__ = "colour-developers@colour-science.org"
__status__ = "Production"
__all__ = [
"exponent_function_basic",
"exponent_function_monitor_curve",
]
[docs]
def exponent_function_basic(
x: ArrayLike,
exponent: ArrayLike = 1,
style: (
Literal[
"basicFwd",
"basicRev",
"basicMirrorFwd",
"basicMirrorRev",
"basicPassThruFwd",
"basicPassThruRev",
]
| str
) = "basicFwd",
) -> NDArrayFloat:
"""
Apply a *basic* exponent transfer function to the specified array.
Parameters
----------
x
Exponentially encoded data :math:`x`.
exponent
Exponent value used for the conversion.
style
Specifies the behaviour for the exponentiation function to operate:
- *basicFwd*: *Basic Forward* exponential behaviour where the
definition applies a basic power law using the exponent.
Values less than zero are clamped.
- *basicRev*: *Basic Reverse* exponential behaviour where the
definition applies a basic power law using the exponent.
Values less than zero are clamped.
- *basicMirrorFwd*: *Basic Mirror Forward* exponential
behaviour where the definition applies a basic power law
using the exponent for values greater than or equal to zero
and mirrors the function for values less than zero (i.e.,
rotationally symmetric around the origin).
- *basicMirrorRev*: *Basic Mirror Reverse* exponential
behaviour where the definition applies a basic power law
using the exponent for values greater than or equal to zero
and mirrors the function for values less than zero (i.e.,
rotationally symmetric around the origin).
- *basicPassThruFwd*: *Basic Pass Forward* exponential
behaviour where the definition applies a basic power law
using the exponent for values greater than or equal to zero
and passes values less than zero unchanged.
- *basicPassThruRev*: *Basic Pass Reverse* exponential
behaviour where the definition applies a basic power law
using the exponent for values greater than or equal to zero
and passes values less than zero unchanged.
Returns
-------
:class:`numpy.ndarray`
Exponentially converted data.
Examples
--------
>>> exponent_function_basic(0.18, 2.2) # doctest: +ELLIPSIS
0.0229932...
>>> exponent_function_basic(-0.18, 2.2)
0.0
>>> exponent_function_basic(0.18, 2.2, "basicRev") # doctest: +ELLIPSIS
0.4586564...
>>> exponent_function_basic(-0.18, 2.2, "basicRev")
0.0
>>> exponent_function_basic( # doctest: +ELLIPSIS
... 0.18, 2.2, "basicMirrorFwd"
... )
0.0229932...
>>> exponent_function_basic( # doctest: +ELLIPSIS
... -0.18, 2.2, "basicMirrorFwd"
... )
-0.0229932...
>>> exponent_function_basic( # doctest: +ELLIPSIS
... 0.18, 2.2, "basicMirrorRev"
... )
0.4586564...
>>> exponent_function_basic( # doctest: +ELLIPSIS
... -0.18, 2.2, "basicMirrorRev"
... )
-0.4586564...
>>> exponent_function_basic( # doctest: +ELLIPSIS
... 0.18, 2.2, "basicPassThruFwd"
... )
0.0229932...
>>> exponent_function_basic( # doctest: +ELLIPSIS
... -0.18, 2.2, "basicPassThruFwd"
... )
-0.1799999...
>>> exponent_function_basic( # doctest: +ELLIPSIS
... 0.18, 2.2, "basicPassThruRev"
... )
0.4586564...
>>> exponent_function_basic( # doctest: +ELLIPSIS
... -0.18, 2.2, "basicPassThruRev"
... )
-0.1799999...
"""
x = as_float_array(x)
exponent = as_float_array(exponent)
style = validate_method(
style,
(
"basicFwd",
"basicRev",
"basicMirrorFwd",
"basicMirrorRev",
"basicPassThruFwd",
"basicPassThruRev",
),
'"{0}" style is invalid, it must be one of {1}!',
)
def exponent_forward(x: NDArrayFloat) -> NDArrayFloat:
"""Return the input raised to the exponent value."""
return x**exponent
def exponent_reverse(y: NDArrayFloat) -> NDArrayFloat:
"""Return the input raised to the inverse exponent value."""
return y ** (as_float_array(1) / exponent)
y = zeros(x.shape)
m_x = x >= 0
if style == "basicfwd":
y[m_x] = exponent_forward(x[m_x])
elif style == "basicrev":
y[m_x] = exponent_reverse(x[m_x])
elif style == "basicmirrorfwd":
y[m_x] = exponent_forward(x[m_x])
y[~m_x] = -exponent_forward(-x[~m_x])
elif style == "basicmirrorrev":
y[m_x] = exponent_reverse(x[m_x])
y[~m_x] = -exponent_reverse(-x[~m_x])
elif style == "basicpassthrufwd":
y[m_x] = exponent_forward(x[m_x])
y[~m_x] = x[~m_x]
else: # style == 'basicpassthrurev'
y[m_x] = exponent_reverse(x[m_x])
y[~m_x] = x[~m_x]
return as_float(y)
[docs]
def exponent_function_monitor_curve(
x: ArrayLike,
exponent: ArrayLike = 1,
offset: ArrayLike = 0,
style: (
Literal[
"monCurveFwd",
"monCurveRev",
"monCurveMirrorFwd",
"monCurveMirrorRev",
]
| str
) = "monCurveFwd",
) -> NDArrayFloat:
"""
Apply the *Monitor Curve* exponent transfer function to the specified array.
Parameters
----------
x
Exponentially encoded data :math:`x`.
exponent
Exponent value used for the conversion.
offset
Offset value used for the conversion.
style
Specifies the behaviour for the exponentiation function to operate:
- *monCurveFwd*: *Monitor Curve Forward* exponential behaviour
where the definition applies a power law function with a
linear segment near the origin.
- *monCurveRev*: *Monitor Curve Reverse* exponential behaviour
where the definition applies a power law function with a
linear segment near the origin.
- *monCurveMirrorFwd*: *Monitor Curve Mirror Forward*
exponential behaviour where the definition applies a power law
function with a linear segment near the origin and mirrors the
function for values less than zero (i.e., rotationally
symmetric around the origin).
- *monCurveMirrorRev*: *Monitor Curve Mirror Reverse*
exponential behaviour where the definition applies a power law
function with a linear segment near the origin and mirrors the
function for values less than zero (i.e., rotationally
symmetric around the origin).
Returns
-------
:class:`numpy.ndarray`
Exponentially converted data.
Examples
--------
>>> exponent_function_monitor_curve(0.18, 2.2, 0.001) # doctest: +ELLIPSIS
0.0232240...
>>> exponent_function_monitor_curve( # doctest: +ELLIPSIS
... -0.18, 2.2, 0.001
... )
-0.0002054...
>>> exponent_function_monitor_curve( # doctest: +ELLIPSIS
... 0.18, 2.2, 0.001, "monCurveRev"
... )
0.4581151...
>>> exponent_function_monitor_curve( # doctest: +ELLIPSIS
... -0.18, 2.2, 0.001, "monCurveRev"
... )
-157.7302795...
>>> exponent_function_monitor_curve( # doctest: +ELLIPSIS
... 0.18, 2.2, 2, "monCurveMirrorFwd"
... )
0.1679399...
>>> exponent_function_monitor_curve( # doctest: +ELLIPSIS
... -0.18, 2.2, 0.001, "monCurveMirrorFwd"
... )
-0.0232240...
>>> exponent_function_monitor_curve( # doctest: +ELLIPSIS
... 0.18, 2.2, 0.001, "monCurveMirrorRev"
... )
0.4581151...
>>> exponent_function_monitor_curve( # doctest: +ELLIPSIS
... -0.18, 2.2, 0.001, "monCurveMirrorRev"
... )
-0.4581151...
"""
x = as_float_array(x)
exponent = as_float_array(exponent)
offset = as_float_array(offset)
style = validate_method(
style,
(
"monCurveFwd",
"monCurveRev",
"monCurveMirrorFwd",
"monCurveMirrorRev",
),
'"{0}" style is invalid, it must be one of {1}!',
)
with sdiv_mode():
s = as_float_array(
sdiv(exponent - 1, offset)
* sdiv(exponent * offset, (exponent - 1) * (offset + 1)) ** exponent
)
def monitor_curve_forward(
x: NDArrayFloat, offset: NDArrayFloat, exponent: NDArrayFloat
) -> NDArrayFloat:
"""Define the *Monitor Curve Forward* function."""
with sdiv_mode():
x_break = sdiv(offset, exponent - 1)
y = as_float_array(x * s)
y[x >= x_break] = ((x[x >= x_break] + offset) / (1 + offset)) ** exponent
return y
def monitor_curve_reverse(
y: NDArrayFloat, offset: NDArrayFloat, exponent: NDArrayFloat
) -> NDArrayFloat:
"""Define the *Monitor Curve Reverse* function."""
with sdiv_mode():
y_break = (
sdiv(exponent * offset, (exponent - 1) * (1 + offset))
) ** exponent
x = as_float_array(y / s)
x[y >= y_break] = ((1 + offset) * (y[y >= y_break] ** (1 / exponent))) - offset
return x
y = zeros(x.shape)
m_x = x >= 0
if style == "moncurvefwd":
y = monitor_curve_forward(x, offset, exponent)
elif style == "moncurverev":
y = monitor_curve_reverse(x, offset, exponent)
elif style == "moncurvemirrorfwd":
y[m_x] = monitor_curve_forward(x[m_x], offset, exponent)
y[~m_x] = -monitor_curve_forward(-x[~m_x], offset, exponent)
else: # style == 'moncurvemirrorrev'
y[m_x] = monitor_curve_reverse(x[m_x], offset, exponent)
y[~m_x] = -monitor_curve_reverse(-x[~m_x], offset, exponent)
return as_float(y)