Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PR: Add "colour.models.rgb.transfer_functions.log" module. #600

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
16 changes: 10 additions & 6 deletions colour/models/rgb/transfer_functions/__init__.py
Expand Up @@ -58,6 +58,7 @@
log_encoding_SLog3, log_decoding_SLog3)
from .srgb import eotf_inverse_sRGB, eotf_sRGB
from .viper_log import log_encoding_ViperLog, log_decoding_ViperLog
from .log import log_encoding_Log2, log_decoding_Log2
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given the module does not depend on anything nor anything really depends on it, please import it in alphabetical order.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted! Will take care of that in the future! 👍


__all__ = ['CV_range', 'legal_to_full', 'full_to_legal']
__all__ += ['gamma_function']
Expand Down Expand Up @@ -116,6 +117,7 @@
]
__all__ += ['eotf_inverse_sRGB', 'eotf_sRGB']
__all__ += ['log_encoding_ViperLog', 'log_decoding_ViperLog']
__all__ += ['log_encoding_Log2', 'log_decoding_Log2']
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alphabetical ordering here too please.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted!


LOG_ENCODINGS = CaseInsensitiveMapping({
'ACEScc': log_encoding_ACEScc,
Expand All @@ -142,7 +144,8 @@
'S-Log3': log_encoding_SLog3,
'T-Log': log_encoding_FilmLightTLog,
'V-Log': log_encoding_VLog,
'ViperLog': log_encoding_ViperLog
'ViperLog': log_encoding_ViperLog,
'Log2': log_encoding_Log2
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likewise :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure :)

})
LOG_ENCODINGS.__doc__ = """
Supported *log* encoding functions.
Expand All @@ -152,7 +155,7 @@
'Canon Log 3', 'Canon Log', 'Cineon', 'D-Log', 'ERIMM RGB', 'F-Log',
'Filmic Pro 6', 'Log3G10', 'Log3G12', 'Panalog', 'PLog', 'Protune',
'REDLog', 'REDLogFilm', 'S-Log', 'S-Log2', 'S-Log3', 'T-Log', 'V-Log',
'ViperLog'}**
'ViperLog', 'Log2'}**
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same ;)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted!

"""


Expand All @@ -170,7 +173,7 @@ def log_encoding(value, function='Cineon', **kwargs):
'Canon Log 3', 'Canon Log', 'Cineon', 'D-Log', 'ERIMM RGB', 'F-Log',
'Filmic Pro 6', 'Log3G10', 'Log3G12', 'Panalog', 'PLog', 'Protune',
'REDLog', 'REDLogFilm', 'S-Log', 'S-Log2', 'S-Log3', 'T-Log',
'V-Log', 'ViperLog'}**,
'V-Log', 'ViperLog', 'Log2'}**,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And same for the remaining instances (even in the docs).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will take care, Thankyou!

Computation function.

Other Parameters
Expand Down Expand Up @@ -286,7 +289,8 @@ def log_encoding(value, function='Cineon', **kwargs):
'S-Log3': log_decoding_SLog3,
'T-Log': log_decoding_FilmLightTLog,
'V-Log': log_decoding_VLog,
'ViperLog': log_decoding_ViperLog
'ViperLog': log_decoding_ViperLog,
'Log2': log_decoding_Log2
})
LOG_DECODINGS.__doc__ = """
Supported *log* decoding functions.
Expand All @@ -296,7 +300,7 @@ def log_encoding(value, function='Cineon', **kwargs):
'Canon Log 3', 'Canon Log', 'Cineon', 'D-Log', 'ERIMM RGB', 'F-Log',
'Filmic Pro 6', 'Log3G10', 'Log3G12', 'Panalog', 'PLog', 'Protune',
'REDLog', 'REDLogFilm', 'S-Log', 'S-Log2', 'S-Log3', 'T-Log', 'V-Log',
'ViperLog'}**
'ViperLog', 'Log2'}**
"""


Expand All @@ -314,7 +318,7 @@ def log_decoding(value, function='Cineon', **kwargs):
'Canon Log 3', 'Canon Log', 'Cineon', 'D-Log', 'ERIMM RGB', 'F-Log',
'Filmic Pro 6', 'Log3G10', 'Log3G12', 'Panalog', 'PLog', 'Protune',
'REDLog', 'REDLogFilm', 'S-Log', 'S-Log2', 'S-Log3', 'T-Log',
'V-Log', 'ViperLog'}**,
'V-Log', 'ViperLog', 'Log2'}**,
Computation function.

Other Parameters
Expand Down
142 changes: 142 additions & 0 deletions colour/models/rgb/transfer_functions/log.py
@@ -0,0 +1,142 @@
#
"""
Log2 Shaper Implementation
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The title could be Common Log Encodings or alike, what do you think @nick-shaw?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would make sense to keep it more general. This module could be expanded later to include things like the generic camera log function for CLF 3.0.

==========================
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Careful at title marker length: ========================== should be shorter

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, should have taken care. Noted anyways!


Defines the *Log2 shaper* log encodings:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Defines the common log encodings:


- :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.-a).
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.-b).
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
"""

from __future__ import division, unicode_literals

import numpy as np
from colour.utilities import from_range_1, to_domain_1

__author__ = 'Colour Developers'
__copyright__ = 'Copyright (C) 2013-2020 - Colour Developers'
__license__ = 'New BSD License - https://opensource.org/licenses/BSD-3-Clause'
__maintainer__ = 'Colour Developers'
__email__ = 'colour-developers@colour-science.org'
__status__ = 'Production'

__all__ = ['log_encoding_Log2', 'log_decoding_Log2']


def log_encoding_Log2(lin,
middle_grey=0.18,
min_exposure=0.18 * 2 ** -6.5,
max_exposure=0.18 * 2 ** 6.5):
"""
Defines the *Log2 shaper* log encoding function.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can drop shaper here, the function is generic enough that it is not necessarily used as a shaper.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that would make sense. Will make the change accordingly! :)


Parameters
----------
lin : numeric or array_like
Linear data to undergo encoding.
middle_grey : numeric, optional
'Middle Grey' exposure value.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want to use quotes, prefer *, e.g. *Middle Grey*, careful at the tabulation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted, will take care.

min_exposure : numeric, optional
Minimum exposure level.
max_exposure : numeric, optional
Maximum exposure level.

Returns
-------
numeric or ndarray
Non-linear *Log2 shaper* encoded data
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Full stop at the end ;)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted!


Notes
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the section is empty, we should get rid of it, maybe here is a good place to mention how this function is used to build various shapers in the ACES OCIO configuration.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, will go about it!

-----

References
----------

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:cite:`TheAcademyofMotionPictureArtsandSciencesa`

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thankyou!

Examples
--------
Linear numeric input gets encoded as follows:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gets is

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure :)


>>> log_encoding_Log2(18)
0.40773288970434662

Linear array-like input gets encoded as follows:

>>> log_encoding_Log2(np.linspace(1, 2, 3))
array([ 0.15174832, 0.18765817, 0.21313661])
"""

lin = to_domain_1(lin)

lg2 = np.log2(lin / middle_grey)
log_norm = (lg2 - min_exposure) / (max_exposure - min_exposure)

return from_range_1(log_norm)


def log_decoding_Log2(log_norm,
middle_grey=0.18,
min_exposure=0.18 * 2 ** -6.5,
max_exposure=0.18 * 2 ** 6.5):
"""
Defines the *Log2 shaper* log decoding function.

Parameters
----------
log_norm : numeric or array_like
Logarithmic data to undergo decoding.
middle_grey : numeric, optional
'Middle Grey' exposure value.
min_exposure : numeric, optional
Minimum exposure level.
max_exposure : numeric, optional
Maximum exposure level.

Returns
-------
numeric or ndarray
Linear *Log2 shaper* decoded data

Notes
-----

References
----------

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:cite:`TheAcademyofMotionPictureArtsandSciencesb`

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thankyou!

Examples
--------
Logarithmic input gets decoded as follows:

>>> log_decoding_Log2(0.40773288970434662)
17.999999999999993

Linear array-like input gets encoded as follows:

>>> log_decoding_Log2(np.linspace(0, 1, 4))
array([ 1.80248299e-01, 7.77032379e+00, 3.34970882e+02,
1.44402595e+04])
"""

log_norm = to_domain_1(log_norm)

lg2 = log_norm * (max_exposure - min_exposure) + min_exposure
lin = (2 ** lg2) * middle_grey

return from_range_1(lin)
175 changes: 175 additions & 0 deletions colour/models/rgb/transfer_functions/tests/test_log.py
@@ -0,0 +1,175 @@
#
"""
Defines unit tests for :mod:`colour.models.rgb.transfer_functions.log`
module.
"""

from __future__ import division, unicode_literals

import numpy as np
import unittest

from colour.models.rgb.transfer_functions import (log_encoding_Log2,
log_decoding_Log2)
from colour.utilities import domain_range_scale, ignore_numpy_errors

__author__ = 'Colour Developers'
__copyright__ = 'Copyright (C) 2013-2020 - Colour Developers'
__license__ = 'New BSD License - https://opensource.org/licenses/BSD-3-Clause'
__maintainer__ = 'Colour Developers'
__email__ = 'colour-developers@colour-science.org'
__status__ = 'Production'

__all__ = [
'TestLogEncoding_Log2',
'TestLogDecoding_Log2',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra coma at the end prevents Yapf inlining.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted

]


class TestLogEncoding_Log2(unittest.TestCase):
"""
Defines :func:`colour.models.rgb.transfer_functions.log.\
log_encoding_Log2` definition unit tests methods.
"""

def test_log_encoding_Log2(self):
"""
Tests :func:`colour.models.rgb.transfer_functions.log.\
log_encoding_Log2` definition.
"""

self.assertAlmostEqual(log_encoding_Log2(18), 0.407732889704, places=7)

self.assertAlmostEqual(
log_encoding_Log2(18, 0.12), 0.443642737727, places=7)

self.assertAlmostEqual(
log_encoding_Log2(18, 0.12, 0.0045), 0.443556955303, places=7)

self.assertAlmostEqual(
log_encoding_Log2(18, 0.12, 0.0045, 15.0),
0.481765775766,
places=7)

def test_n_dimensional_log_encoding_Log2(self):
"""
Tests :func:`colour.models.rgb.transfer_functions.log.\
log_encoding_Log2` definition n-dimensional arrays support.
"""

x = 18
y = log_encoding_Log2(x)

x = np.tile(x, 6)
y = np.tile(y, 6)
np.testing.assert_almost_equal(log_encoding_Log2(x), y, decimal=7)

x = np.reshape(x, (2, 3))
y = np.reshape(y, (2, 3))
np.testing.assert_almost_equal(log_encoding_Log2(x), y, decimal=7)

x = np.reshape(x, (2, 3, 1))
y = np.reshape(y, (2, 3, 1))
np.testing.assert_almost_equal(log_encoding_Log2(x), y, decimal=7)

def test_domain_range_scale_log_encoding_Log2(self):
"""
Tests :func:`colour.models.rgb.transfer_functions.log.\
log_encoding_Log2` definition domain and range scale support.
"""

x = 18
y = log_encoding_Log2(x)

d_r = (('reference', 1), (1, 1), (100, 100))
for scale, factor in d_r:
with domain_range_scale(scale):
np.testing.assert_almost_equal(
log_encoding_Log2(x * factor), y * factor, decimal=7)

@ignore_numpy_errors
def test_nan_log_encoding_Log2(self):
"""
Tests :func:`colour.models.rgb.transfer_functions.log.\
log_encoding_Log2` definition nan support.
"""

log_encoding_Log2(np.array([-1.0, 0.0, 1.0, -np.inf, np.inf, np.nan]))


class TestLogDecoding_Log2(unittest.TestCase):
"""
Defines :func:`colour.models.rgb.transfer_functions.log.\
log_decoding_Log2` definition unit tests methods.
"""

def test_log_decoding_Log2(self):
"""
Tests :func:`colour.models.rgb.transfer_functions.log.\
log_decoding_Log2` definition.
"""

self.assertAlmostEqual(
log_decoding_Log2(0.40773288970434662), 17.9999999991, places=7)

self.assertAlmostEqual(
log_decoding_Log2(0.4077328897, 0.12), 11.9999999994, places=7)

self.assertAlmostEqual(
log_decoding_Log2(0.4077328897, 0.12, 0.0045),
12.0123777083,
places=7)

self.assertAlmostEqual(
log_decoding_Log2(0.4077328897, 0.12, 0.0045, 15.0),
8.33836692466,
places=7)

def test_n_dimensional_log_decoding_Log2(self):
"""
Tests :func:`colour.models.rgb.transfer_functions.log.\
log_decoding_Log2` definition n-dimensional arrays support.
"""

y = 0.384970815928670
x = log_decoding_Log2(y)

y = np.tile(y, 6)
x = np.tile(x, 6)
np.testing.assert_almost_equal(log_decoding_Log2(y), x, decimal=7)

y = np.reshape(y, (2, 3))
x = np.reshape(x, (2, 3))
np.testing.assert_almost_equal(log_decoding_Log2(y), x, decimal=7)

y = np.reshape(y, (2, 3, 1))
x = np.reshape(x, (2, 3, 1))
np.testing.assert_almost_equal(log_decoding_Log2(y), x, decimal=7)

def test_domain_range_scale_log_decoding_Log2(self):
"""
Tests :func:`colour.models.rgb.transfer_functions.log.\
log_decoding_Log2` definition domain and range scale support.
"""

y = 0.384970815928670
x = log_decoding_Log2(y)

d_r = (('reference', 1), (1, 1), (100, 100))
for scale, factor in d_r:
with domain_range_scale(scale):
np.testing.assert_almost_equal(
log_decoding_Log2(y * factor), x * factor, decimal=7)

@ignore_numpy_errors
def test_nan_log_decoding_Log2(self):
"""
Tests :func:`colour.models.rgb.transfer_functions.log.\
log_decoding_Log2` definition nan support.
"""

log_decoding_Log2(np.array([-1.0, 0.0, 1.0, -np.inf, np.inf, np.nan]))


if __name__ == '__main__':
unittest.main()
2 changes: 2 additions & 0 deletions docs/colour.models.rst
Expand Up @@ -588,6 +588,8 @@ Log Encoding and Decoding
log_decoding_VLog
log_encoding_ViperLog
log_decoding_ViperLog
log_encoding_Log2
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alphabetical ordering please :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted, Thankyou!

log_decoding_Log2

Colour Encodings
~~~~~~~~~~~~~~~~
Expand Down