Source code for colour.models.rgb.transfer_functions.log

"""
Common Log Encodings
====================

Define the common log encodings.

-   :func:`colour.models.logarithmic_function_basic`
-   :func:`colour.models.logarithmic_function_quasilog`
-   :func:`colour.models.logarithmic_function_camera`
-   :func:`colour.models.log_encoding_Log2`
-   :func:`colour.models.log_decoding_Log2`

References
----------
-   :cite:`TheAcademyofMotionPictureArtsandSciencesa` :
    The Academy of Motion Picture Arts and Sciences,
    Science and Technology Council,
    & Academy Color Encoding System (ACES) Project Subcommittee.(n.d.).
    ACESutil.Lin_to_Log2_param.ctl. Retrieved June 14, 2020,
    from https://github.com/ampas/aces-dev/blob/\
518c27f577e99cdecfddf2ebcfaa53444b1f9343/transforms/ctl/utilities/\
ACESutil.Lin_to_Log2_param.ctl
-   :cite:`TheAcademyofMotionPictureArtsandSciencesb` :
    The Academy of Motion Picture Arts and Sciences,
    Science and Technology Council,
    & Academy Color Encoding System (ACES) Project Subcommittee.(n.d.).
    ACESutil.Log2_to_Lin_param.ctl. Retrieved June 14, 2020,
    from https://github.com/ampas/aces-dev/blob/\
518c27f577e99cdecfddf2ebcfaa53444b1f9343/transforms/ctl/utilities/\
ACESutil.Log2_to_Lin_param.ctl
:   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

import numpy as np

from colour.algebra import sdiv, sdiv_mode

if typing.TYPE_CHECKING:
    from colour.hints import (
        ArrayLike,
        Literal,
        NDArrayFloat,
    )

from colour.hints import cast
from colour.utilities import (
    as_float,
    as_float_array,
    optional,
    validate_method,
)

__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__ = [
    "logarithmic_function_basic",
    "logarithmic_function_quasilog",
    "logarithmic_function_camera",
    "log_encoding_Log2",
    "log_decoding_Log2",
]

FLT_MIN = 1.175494e-38


[docs] def logarithmic_function_basic( x: ArrayLike, style: ( Literal["log10", "antiLog10", "log2", "antiLog2", "logB", "antiLogB"] | str ) = "log2", base: int = 2, ) -> NDArrayFloat: """ Apply a logarithmic or anti-logarithmic transformation to the specified array. Parameters ---------- x Logarithmically encoded data :math:`x`. style Specifies the behaviour for the logarithmic function to operate: - *log10*: Applies a base 10 logarithm to the passed value. - *antiLog10*: Applies a base 10 anti-logarithm to the passed value. - *log2*: Applies a base 2 logarithm to the passed value. - *antiLog2*: Applies a base 2 anti-logarithm to the passed value. - *logB*: Applies an arbitrary base logarithm to the passed value. - *antiLogB*: Applies an arbitrary base anti-logarithm to the passed value. base Logarithmic base used for the conversion. Returns ------- :class:`numpy.ndarray` Logarithmically transformed data. Examples -------- The basic logarithmic function *styles* operate as follows: >>> logarithmic_function_basic(0.18) # doctest: +ELLIPSIS np.float64(-2.4739311...) >>> logarithmic_function_basic(0.18, "log10") # doctest: +ELLIPSIS np.float64(-0.7447274...) >>> logarithmic_function_basic(0.18, "logB", 3) # doctest: +ELLIPSIS np.float64(-1.5608767...) >>> logarithmic_function_basic( # doctest: +ELLIPSIS ... -2.473931188332412, "antiLog2" ... ) np.float64(0.18000000...) >>> logarithmic_function_basic( # doctest: +ELLIPSIS ... -0.7447274948966939, "antiLog10" ... ) np.float64(0.18000000...) >>> logarithmic_function_basic( # doctest: +ELLIPSIS ... -1.5608767950073117, "antiLogB", 3 ... ) np.float64(0.18000000...) """ x = as_float_array(x) style = validate_method( style, ("log10", "antiLog10", "log2", "antiLog2", "logB", "antiLogB"), '"{0}" style is invalid, it must be one of {1}!', ) if style == "log10": return as_float(np.where(x >= FLT_MIN, np.log10(x), np.log10(FLT_MIN))) if style == "antilog10": return as_float(10**x) if style == "log2": return as_float(np.where(x >= FLT_MIN, np.log2(x), np.log2(FLT_MIN))) if style == "antilog2": return as_float(2**x) if style == "logb": return as_float(np.log(x) / np.log(base)) # style == 'antilogb' return as_float(base**x)
[docs] def logarithmic_function_quasilog( x: ArrayLike, style: Literal["linToLog", "logToLin"] | str = "linToLog", base: int = 2, log_side_slope: float = 1, lin_side_slope: float = 1, log_side_offset: float = 0, lin_side_offset: float = 0, ) -> NDArrayFloat: """ Apply the *Quasilog* logarithmic function for encoding and decoding. This function implements a logarithmic transformation with configurable slopes and offsets for both linear and logarithmic sides. Parameters ---------- x Logarithmically encoded data :math:`x`. style Specifies the behaviour for the logarithmic function to operate: - *linToLog*: Apply a logarithm to convert linear data to logarithmic data. - *logToLin*: Apply an anti-logarithm to convert logarithmic data to linear data. base Logarithmic base used for the conversion. log_side_slope Slope (or gain) applied to the log side of the logarithmic function. The default value is 1. lin_side_slope Slope of the linear side of the logarithmic function. The default value is 1. log_side_offset Offset applied to the log side of the logarithmic function. The default value is 0. lin_side_offset Offset applied to the linear side of the logarithmic function. The default value is 0. Returns ------- :class:`numpy.ndarray` Encoded/Decoded data. Examples -------- >>> logarithmic_function_quasilog(0.18, "linToLog") # doctest: +ELLIPSIS np.float64(-2.4739311...) >>> logarithmic_function_quasilog( # doctest: +ELLIPSIS ... -2.473931188332412, "logToLin" ... ) np.float64(0.18000000...) """ x = as_float_array(x) style = validate_method( style, ("lintolog", "logtolin"), '"{0}" style is invalid, it must be one of {1}!', ) if style == "lintolog": y = ( log_side_slope * ( np.log(np.maximum(lin_side_slope * x + lin_side_offset, FLT_MIN)) / np.log(base) ) + log_side_offset ) else: # style == 'logtolin' with sdiv_mode(): y = sdiv( base ** sdiv(x - log_side_offset, log_side_slope) - lin_side_offset, lin_side_slope, ) return as_float(y)
[docs] def logarithmic_function_camera( x: ArrayLike, style: (Literal["cameraLinToLog", "cameraLogToLin"] | str) = "cameraLinToLog", base: int = 2, log_side_slope: float = 1, lin_side_slope: float = 1, log_side_offset: float = 0, lin_side_offset: float = 0, lin_side_break: float = 0.005, linear_slope: float | None = None, ) -> NDArrayFloat: """ Apply a camera logarithmic function to the specified array. Apply a piece-wise function with logarithmic and linear segments for encoding or decoding camera data. Parameters ---------- x Logarithmically encoded data :math:`x`. style Specifies the behaviour for the logarithmic function to operate: - *cameraLinToLog*: Applies a piece-wise function with logarithmic and linear segments on linear values, converting them to non-linear values. - *cameraLogToLin*: Applies a piece-wise function with logarithmic and linear segments on non-linear values, converting them to linear values. base Logarithmic base used for the conversion. log_side_slope Slope (or gain) applied to the log side of the logarithmic function. The default value is 1. lin_side_slope Slope of the linear side of the logarithmic function. The default value is 1. log_side_offset Offset applied to the log side of the logarithmic function. The default value is 0. lin_side_offset Offset applied to the linear side of the logarithmic function. The default value is 0. lin_side_break Break-point, defined in linear space, at which the piece-wise function transitions between the logarithmic and linear segments. linear_slope Slope of the linear portion of the curve. The default value is *None*. Returns ------- :class:`numpy.ndarray` Encoded/Decoded data. Examples -------- >>> logarithmic_function_camera( # doctest: +ELLIPSIS ... 0.18, "cameraLinToLog" ... ) np.float64(-2.4739311...) >>> logarithmic_function_camera( # doctest: +ELLIPSIS ... -2.4739311883324122, "cameraLogToLin" ... ) np.float64(0.1800000...) """ x = as_float_array(x) style = validate_method( style, ("cameraLinToLog", "cameraLogToLin"), '"{0}" style is invalid, it must be one of {1}!', ) log_side_break = ( log_side_slope * (np.log(lin_side_slope * lin_side_break + lin_side_offset) / np.log(base)) + log_side_offset ) with sdiv_mode(): linear_slope = cast( "float", optional( linear_slope, ( log_side_slope * ( sdiv( lin_side_slope, (lin_side_slope * lin_side_break + lin_side_offset) * np.log(base), ) ) ), ), ) linear_offset = log_side_break - linear_slope * lin_side_break if style == "cameralintolog": m_x = x <= lin_side_break y = np.where( m_x, linear_slope * x + linear_offset, logarithmic_function_quasilog( x, "linToLog", base, log_side_slope, lin_side_slope, log_side_offset, lin_side_offset, ), ) else: # style == 'cameralogtolin' with sdiv_mode(): m_x = x <= log_side_break y = np.where( m_x, sdiv(x - linear_offset, linear_slope), logarithmic_function_quasilog( x, "logToLin", base, log_side_slope, lin_side_slope, log_side_offset, lin_side_offset, ), ) return as_float(y)
[docs] def log_encoding_Log2( lin: ArrayLike, middle_grey: float = 0.18, min_exposure: float = -6.5, max_exposure: float = 6.5, ) -> NDArrayFloat: """ Apply the common *Log2* log encoding opto-electronic transfer function (OETF). Parameters ---------- lin Linear *Log2* decoded data. middle_grey *Middle Grey* exposure value. min_exposure Minimum exposure level. max_exposure Maximum exposure level. Returns ------- :class:`numpy.ndarray` Non-linear *Log2* encoded data. Notes ----- +--------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +==============+=======================+===============+ | ``lin`` | 1 | 1 | +--------------+-----------------------+---------------+ +--------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +==============+=======================+===============+ | ``log_norm`` | 1 | 1 | +--------------+-----------------------+---------------+ - The common *Log2* encoding function can be used to build linear to logarithmic shapers in the *ACES OCIO configuration*. - A (48-nits OCIO) shaper having values in a linear domain, can be encoded to a logarithmic domain: +-------------------+-------------------+ | **Shaper Domain** | **Shaper Range** | +===================+===================+ | [0.002, 16.291] | [0, 1] | +-------------------+-------------------+ References ---------- :cite:`TheAcademyofMotionPictureArtsandSciencesa` Examples -------- >>> log_encoding_Log2(0.18) np.float64(0.5) """ lin = as_float_array(lin) lg2 = np.log2(lin / middle_grey) log_norm = (lg2 - min_exposure) / (max_exposure - min_exposure) return as_float(log_norm)
[docs] def log_decoding_Log2( log_norm: ArrayLike, middle_grey: float = 0.18, min_exposure: float = -6.5, max_exposure: float = 6.5, ) -> NDArrayFloat: """ Apply the common *Log2* log decoding inverse opto-electronic transfer function (OETF). Parameters ---------- log_norm Non-linear *Log2* encoded data. middle_grey *Middle Grey* exposure value. min_exposure Minimum exposure level. max_exposure Maximum exposure level. Returns ------- :class:`numpy.ndarray` Linear *Log2* decoded data. Notes ----- +--------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +==============+=======================+===============+ | ``log_norm`` | 1 | 1 | +--------------+-----------------------+---------------+ +--------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +==============+=======================+===============+ | ``lin`` | 1 | 1 | +--------------+-----------------------+---------------+ - The common *Log2* decoding function can be used to build logarithmic to linear shapers in the *ACES OCIO configuration*. - The shaper with logarithmic encoded values can be decoded back to linear domain: +-------------------+-------------------+ | **Shaper Range** | **Shaper Domain** | +===================+===================+ | [0, 1] | [0.002, 16.291] | +-------------------+-------------------+ References ---------- :cite:`TheAcademyofMotionPictureArtsandSciencesb` Examples -------- >>> log_decoding_Log2(0.5) # doctest: +ELLIPSIS np.float64(0.18) """ log_norm = as_float_array(log_norm) lg2 = log_norm * (max_exposure - min_exposure) + min_exposure lin = (2**lg2) * middle_grey return as_float(lin)