"""
Digital Still Camera Exposure
=============================
Defines various objects for modeling Digital Still Camera (DSC) exposure:
- :func:`colour_hdri.focal_plane_exposure`
- :func:`colour_hdri.arithmetic_mean_focal_plane_exposure`
- :func:`colour_hdri.saturation_based_speed_focal_plane_exposure`
- :func:`colour_hdri.exposure_index_values`
- :func:`colour_hdri.exposure_value_100`
- :func:`colour_hdri.photometric_exposure_scale_factor_Lagarde2014`
References
----------
- :cite:`ISO2006` : ISO. (2006). INTERNATIONAL STANDARD ISO12232-2006 -
Photography - Digital still cameras - Determination of exposure index, ISO
speed ratings, standard output sensitivity, and recommended exposure index.
- :cite:`Lagarde2014` : Lagarde, Sébastian, & de Rousiers, C. (2014). Moving
Frostbite to Physically Based Rendering 3.0. Siggraph 2014, 119.
"""
from __future__ import annotations
import numpy as np
from colour.hints import ArrayLike, NDArrayFloat
from colour.utilities import as_float, as_float_array
from colour_hdri.exposure import (
average_luminance,
luminance_to_exposure_value,
)
__author__ = "Colour Developers"
__copyright__ = "Copyright 2015 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__ = [
"q_factor",
"focal_plane_exposure",
"arithmetic_mean_focal_plane_exposure",
"saturation_based_speed_focal_plane_exposure",
"exposure_index_values",
"exposure_value_100",
"photometric_exposure_scale_factor_Lagarde2014",
]
def q_factor(
T: ArrayLike = 9 / 10,
f_v: ArrayLike = 98 / 100,
theta: ArrayLike = 10,
) -> NDArrayFloat:
"""
Compute the :math:`q` factor modeling the total lens vignetting and
transmission attenuation.
Parameters
----------
T
Transmission factor of the lens :math:`T`.
f_v
Vignetting factor :math:`f_v`.
theta
Angle of image point off axis :math:`\\theta`.
Returns
-------
:class:`numpy.ndarray`
:math:`q` factor.
References
----------
:cite:`ISO2006`
Examples
--------
>>> q_factor() # doctest: +ELLIPSIS
0.6515748...
"""
T = as_float_array(T)
f_v = as_float_array(f_v)
theta = as_float_array(theta)
return as_float(np.pi / 4 * T * f_v * np.cos(np.radians(theta)) ** 4)
[docs]
def focal_plane_exposure(
L: ArrayLike,
A: ArrayLike,
t: ArrayLike,
F: ArrayLike,
i: ArrayLike,
H_f: ArrayLike,
T: ArrayLike = 9 / 10,
f_v: ArrayLike = 98 / 100,
theta: ArrayLike = 10,
) -> NDArrayFloat:
"""
Compute the focal plane exposure :math:`H` in lux-seconds (:math:`lx.s`).
Parameters
----------
L
Scene luminance :math:`L`, expressed in :math:`cd/m^2`.
A
Lens *F-Number* :math:`A`.
t
*Exposure Time* :math:`t`, expressed in seconds.
F
Lens focal length :math:`F`, expressed in meters.
i
Image distance :math:`i`, expressed in meters.
H_f
Focal plane flare exposure :math:`H_f`, expressed in lux-seconds
(:math:`lx.s`).
T
Transmission factor of the lens :math:`T`.
f_v
Vignetting factor :math:`f_v`.
theta
Angle of image point off axis :math:`\\theta`.
Returns
-------
:class:`numpy.ndarray`
Focal plane exposure :math:`H` in lux-seconds (:math:`lx.s`).
Notes
-----
- Focal plane exposure is also named luminous exposure or photometric
exposure and is time-integrated illuminance.
- Object distance :math:`o`, focal length :math:`F`, and image distance
:math:`i` are related by the thin-lens equation:
:math:`\\cfrac{1}{f}=\\cfrac{1}{o}+\\cfrac{1}{i}`
- This method ignores the *ISO* arithmetic speed :math:`S` and is not
concerned with determining an appropriate minimum or maximum exposure
level.
References
----------
:cite:`ISO2006`
Examples
--------
>>> focal_plane_exposure(4000, 8, 1 / 250, 50 / 1000, 50 / 1000, 0.0015)
... # doctest: +ELLIPSIS
0.1643937...
"""
L = as_float_array(L)
t = as_float_array(t)
A = as_float_array(A)
F = as_float_array(F)
i = as_float_array(i)
H_f = as_float_array(H_f)
q = q_factor(T, f_v, theta)
H = q * (L * t * F**2) / (A**2 * i**2) + H_f
return as_float(H)
[docs]
def arithmetic_mean_focal_plane_exposure(
L_a: ArrayLike, A: ArrayLike, t: ArrayLike
) -> NDArrayFloat:
"""
Compute the arithmetic mean focal plane exposure :math:`H_a` for a camera
focused on infinity, :math:`H_f << H`, :math:`T=9/10`,
:math:`\\theta =10^{\\circ}` and :math:`f_v=98/100`.
Parameters
----------
L_a
Arithmetic scene luminance :math:`L_a`, expressed in :math:`cd/m^2`.
A
Lens *F-Number* :math:`A`.
t
*Exposure Time* :math:`t`, expressed in seconds.
Returns
-------
:class:`numpy.ndarray`
Focal plane exposure :math:`H_a`.
Notes
-----
- Focal plane exposure is also named luminous exposure or photometric
exposure and is time-integrated illuminance.
- Object distance :math:`o`, focal length :math:`F`, and image distance
:math:`i` are related by the thin-lens equation:
:math:`\\cfrac{1}{f}=\\cfrac{1}{o}+\\cfrac{1}{i}`
- This method ignores the *ISO* arithmetic speed :math:`S` and is not
concerned with determining an appropriate minimum or maximum exposure
level.
References
----------
:cite:`ISO2006`
Examples
--------
>>> arithmetic_mean_focal_plane_exposure(4000, 8, 1 / 250)
... # doctest: +ELLIPSIS
0.1628937...
"""
H_a = focal_plane_exposure(L_a, A, t, 1, 1, 0, 9 / 10, 98 / 100, 10)
return H_a
[docs]
def saturation_based_speed_focal_plane_exposure(
L: ArrayLike,
A: ArrayLike,
t: ArrayLike,
S: ArrayLike,
F: ArrayLike = 50 / 1000,
i: ArrayLike = 1 / (-1 / 5 + 1 / (50 / 1000)),
H_f: ArrayLike = 0,
T: ArrayLike = 9 / 10,
f_v: ArrayLike = 98 / 100,
theta: ArrayLike = 10,
) -> NDArrayFloat:
"""
Compute the Saturation-Based Speed (SBS) focal plane exposure
:math:`H_{SBS}` in lux-seconds (:math:`lx.s`).
The model implemented by this definition is appropriate to simulate a
physical camera in an offline or realtime renderer.
Parameters
----------
L
Scene luminance :math:`L`, expressed in :math:`cd/m^2`.
A
Lens *F-Number* :math:`A`.
t
*Exposure Time* :math:`t`, expressed in seconds.
S
*ISO* arithmetic speed :math:`S`.
F
Lens focal length :math:`F`, expressed in meters.
i
Image distance :math:`i`, expressed in meters.
H_f
Focal plane flare exposure :math:`H_f`, expressed in lux-seconds
(:math:`lx.s`).
T
Transmission factor of the lens :math:`T`.
f_v
Vignetting factor :math:`f_v`.
theta
Angle of image point off axis :math:`\\theta`.
Returns
-------
:class:`numpy.ndarray`
Saturation-Based Speed focal plane exposure :math:`H_{SBS}` in
lux-seconds (:math:`lx.s`).
Notes
-----
- Focal plane exposure is also named luminous exposure or photometric
exposure and is time-integrated illuminance.
- Object distance :math:`o`, focal length :math:`F`, and image distance
:math:`i` are related by the thin-lens equation:
:math:`\\cfrac{1}{f}=\\cfrac{1}{o}+\\cfrac{1}{i}`
- The image distance default value is that of an object located at 5m and
imaged with a 50mm lens.
- The saturation based speed, :math:`S_{sat}`, of an electronic still
picture camera is defined as: :math:`S_{sat}=\\cfrac{78}{H_{sat}}`
where :math:`H_{sat}` is the minimum focal plane exposure, expressed in
lux-seconds (:math:`lx.s`), that produces the maximum valid (not
clipped or bloomed) camera output signal. This provides :math:`1/2`
"stop" of headroom (41% additional headroom) for specular highlights
above the signal level that would be obtained from a theoretical 100%
reflectance object in the scene, so that a theoretical 141% reflectance
object in the scene would produce a focal plane exposure of
:math:`H_{sat}`.
- The focal plane exposure :math:`H_{SBS}` computed by this definition is
almost equal to that given by scene luminance :math:`L` scaled with
the output of :func:`colour_hdri.\
photometric_exposure_scale_factor_Lagarde2014` definition.
References
----------
:cite:`ISO2006`
Examples
--------
>>> saturation_based_speed_focal_plane_exposure( # doctest: +ELLIPSIS
... 4000, 8, 1 / 250, 400, 50 / 1000, 50 / 1000, 0.0015
... )
0.8430446...
"""
S = as_float_array(S)
H = focal_plane_exposure(L, A, t, F, i, H_f, T, f_v, theta)
H_SBS = H * S / 78
return as_float(H_SBS)
[docs]
def exposure_index_values(H_a: ArrayLike) -> NDArrayFloat:
"""
Compute the exposure index values :math:`I_{EI}` from given focal plane
exposure :math:`H_a`.
Parameters
----------
H_a
Focal plane exposure :math:`H_a`.
Returns
-------
:class:`numpy.ndarray`
Exposure index values :math:`I_{EI}`.
References
----------
:cite:`ISO2006`
Examples
--------
>>> exposure_index_values(0.1628937086212269) # doctest: +ELLIPSIS
61.3897251...
"""
return as_float(10 / as_float_array(H_a))
[docs]
def exposure_value_100(N: ArrayLike, t: ArrayLike, S: ArrayLike) -> NDArrayFloat:
"""
Compute the exposure value :math:`EV100` from given relative aperture
*F-Number* :math:`N`, *Exposure Time* :math:`t` and *ISO* arithmetic
speed :math:`S`.
Parameters
----------
N
Relative aperture *F-Number* :math:`N`.
t
*Exposure Time* :math:`t`.
S
*ISO* arithmetic speed :math:`S`.
Returns
-------
:class:`numpy.ndarray`
Exposure value :math:`EV100`.
References
----------
:cite:`ISO2006`, :cite:`Lagarde2014`
Notes
-----
- The underlying implementation uses the
:func:`colour_hdri.luminance_to_exposure_value` and
:func:`colour_hdri.average_luminance` definitions with same fixed value
for the *reflected light calibration constant* :math:`k` which cancels
its scaling effect and produces a value equal to
:math:`log_2(\\cfrac{N^2}{t}) - log_2(\\cfrac{S}{100})` as given in
:cite:`Lagarde2014`.
Examples
--------
>>> exposure_value_100(8, 1 / 250, 400) # doctest: +ELLIPSIS
11.9657842...
"""
return luminance_to_exposure_value(average_luminance(N, t, S), 100)
[docs]
def photometric_exposure_scale_factor_Lagarde2014(
EV100: ArrayLike,
T: ArrayLike = 9 / 10,
f_v: ArrayLike = 98 / 100,
theta: ArrayLike = 10,
) -> NDArrayFloat:
"""
Convert the exposure value :math:`EV100` to photometric exposure scale
factor using *Lagarde and de Rousiers (2014)* formulation derived from the
*ISO 12232:2006* *Saturation Based Sensitivity* (SBS) recommendation.
The model implemented by this definition is appropriate to simulate a
physical camera in an offline or realtime renderer.
Parameters
----------
T
Exposure value :math:`EV100`.
T
Transmission factor of the lens :math:`T`.
f_v
Vignetting factor :math:`f_v`.
theta
Angle of image point off axis :math:`\\theta`.
Returns
-------
:class:`numpy.ndarray`
Photometric exposure in lux-seconds (:math:`lx.s`).
Notes
-----
- The saturation based speed, :math:`S_{sat}`, of an electronic still
picture camera is defined as: :math:`S_{sat}=\\cfrac{78}{H_{sat}}`
where :math:`H_{sat}` is the minimum focal plane exposure, expressed in
lux-seconds (:math:`lx.s`), that produces the maximum valid (not
clipped or bloomed) camera output signal. This provides :math:`1/2`
"stop" of headroom (41% additional headroom) for specular highlights
above the signal level that would be obtained from a theoretical 100%
reflectance object in the scene, so that a theoretical 141% reflectance
object in the scene would produce a focal plane exposure of
:math:`H_{sat}`.
- Scene luminance :math:`L` scaled with the photometric exposure value
computed by this definition is almost equal to that given by the
:func:`colour_hdri.saturation_based_speed_focal_plane_exposure`
definition.
References
----------
:cite:`ISO2006`, :cite:`Lagarde2014`
Examples
--------
>>> EV100 = exposure_value_100(8, 1 / 250, 400)
>>> H = photometric_exposure_scale_factor_Lagarde2014(EV100)
>>> print(H) # doctest: +ELLIPSIS
0.0002088...
>>> H * 4000 # doctest: +ELLIPSIS
0.8353523...
"""
EV100 = as_float_array(EV100)
q = q_factor(T, f_v, theta)
return as_float(1 / (78 / (100 * q) * 2**EV100))