colour.LUT3D#

class colour.LUT3D(table: ArrayLike | None = None, name: str | None = None, domain: ArrayLike | None = None, size: ArrayLike | None = None, comments: Sequence | None = None)[source]#

Bases: AbstractLUT

Define the base class for a 3-dimensional lookup table (3D LUT).

This class provides a foundation for working with 3D lookup tables, which map input colour values through a discretized 3D grid to output colour values. The table operates on three input channels simultaneously, making it suitable for RGB-to-RGB colour transformations and other tristimulus colour space operations.

Parameters:
  • table (ArrayLike | None) – Underlying LUT table.

  • name (str | None) – LUT name.

  • domain (ArrayLike | None) – LUT domain, also used to define the instantiation time default table domain.

  • size (ArrayLike | None) – Size of the instantiation time default table, default to 33.

  • comments (Sequence | None) – Comments to add to the LUT.

Methods

Examples

Instantiating a unity LUT with a table with 16x16x16x3 elements:

>>> print(LUT3D(size=16))
LUT3D - Unity 16
----------------

Dimensions : 3
Domain     : [[ 0.  0.  0.]
              [ 1.  1.  1.]]
Size       : (16, 16, 16, 3)

Instantiating a LUT using a custom table with 16x16x16x3 elements:

>>> print(LUT3D(LUT3D.linear_table(16) ** (1 / 2.2)))
LUT3D - ...
--------...

Dimensions : 3
Domain     : [[ 0.  0.  0.]
              [ 1.  1.  1.]]
Size       : (16, 16, 16, 3)

Instantiating a LUT using a custom table with 16x16x16x3 elements, custom name, custom domain and comments:

>>> from colour.algebra import spow
>>> domain = np.array([[-0.1, -0.2, -0.4], [1.5, 3.0, 6.0]])
>>> print(
...     LUT3D(
...         spow(LUT3D.linear_table(16), 1 / 2.2),
...         "My LUT",
...         domain,
...         comments=["A first comment.", "A second comment."],
...     )
... )
LUT3D - My LUT
--------------

Dimensions : 3
Domain     : [[-0.1 -0.2 -0.4]
              [ 1.5  3.   6. ]]
Size       : (16, 16, 16, 3)
Comment 01 : A first comment.
Comment 02 : A second comment.
__init__(table: ArrayLike | None = None, name: str | None = None, domain: ArrayLike | None = None, size: ArrayLike | None = None, comments: Sequence | None = None) None[source]#
Parameters:
Return type:

None

is_domain_explicit() bool[source]#

Return whether the LUT domain is explicit (or implicit).

An implicit domain is defined by its shape only:

[[0 0 0]
 [1 1 1]]

While an explicit domain defines every single discrete sample:

[[0.0 0.0 0.0]
 [0.1 0.1 0.1]
 [0.2 0.2 0.2]
 [0.3 0.3 0.3]
 [0.4 0.4 0.4]
 [0.8 0.8 0.8]
 [1.0 1.0 1.0]]
Returns:

Is LUT domain explicit.

Return type:

bool

Examples

>>> LUT3D().is_domain_explicit()
False
>>> domain = np.array([[-0.1, -0.2, -0.4], [0.7, 1.4, 6.0], [1.5, 3.0, np.nan]])
>>> LUT3D(domain=domain).is_domain_explicit()
True
static linear_table(size: TypeAliasForwardRef('ArrayLike') | None = None, domain: TypeAliasForwardRef('ArrayLike') | None = None) NDArrayFloat[source]#

Generate a linear table with the specified size and domain.

The number of output samples \(n\) is equal to size**3 * 3 or size[0] * size[1] * size[2] * 3.

Parameters:
  • size (TypeAliasForwardRef('ArrayLike') | None) – Expected table size, default to 33.

  • domain (TypeAliasForwardRef('ArrayLike') | None) – Domain of the table.

Returns:

Linear table with size**3 * 3 or size[0] * size[1] * size[2] * 3 samples.

Return type:

numpy.ndarray

Examples

>>> LUT3D.linear_table(3, np.array([[-0.1, -0.2, -0.4], [1.5, 3.0, 6.0]]))
array([[[[-0.1, -0.2, -0.4],
         [-0.1, -0.2,  2.8],
         [-0.1, -0.2,  6. ]],

        [[-0.1,  1.4, -0.4],
         [-0.1,  1.4,  2.8],
         [-0.1,  1.4,  6. ]],

        [[-0.1,  3. , -0.4],
         [-0.1,  3. ,  2.8],
         [-0.1,  3. ,  6. ]]],


       [[[ 0.7, -0.2, -0.4],
         [ 0.7, -0.2,  2.8],
         [ 0.7, -0.2,  6. ]],

        [[ 0.7,  1.4, -0.4],
         [ 0.7,  1.4,  2.8],
         [ 0.7,  1.4,  6. ]],

        [[ 0.7,  3. , -0.4],
         [ 0.7,  3. ,  2.8],
         [ 0.7,  3. ,  6. ]]],


       [[[ 1.5, -0.2, -0.4],
         [ 1.5, -0.2,  2.8],
         [ 1.5, -0.2,  6. ]],

        [[ 1.5,  1.4, -0.4],
         [ 1.5,  1.4,  2.8],
         [ 1.5,  1.4,  6. ]],

        [[ 1.5,  3. , -0.4],
         [ 1.5,  3. ,  2.8],
         [ 1.5,  3. ,  6. ]]]])
>>> LUT3D.linear_table(
...     np.array([3, 3, 2]),
...     np.array([[-0.1, -0.2, -0.4], [1.5, 3.0, 6.0]]),
... )
array([[[[-0.1, -0.2, -0.4],
         [-0.1, -0.2,  6. ]],

        [[-0.1,  1.4, -0.4],
         [-0.1,  1.4,  6. ]],

        [[-0.1,  3. , -0.4],
         [-0.1,  3. ,  6. ]]],


       [[[ 0.7, -0.2, -0.4],
         [ 0.7, -0.2,  6. ]],

        [[ 0.7,  1.4, -0.4],
         [ 0.7,  1.4,  6. ]],

        [[ 0.7,  3. , -0.4],
         [ 0.7,  3. ,  6. ]]],


       [[[ 1.5, -0.2, -0.4],
         [ 1.5, -0.2,  6. ]],

        [[ 1.5,  1.4, -0.4],
         [ 1.5,  1.4,  6. ]],

        [[ 1.5,  3. , -0.4],
         [ 1.5,  3. ,  6. ]]]])
>>> domain = np.array([[-0.1, -0.2, -0.4], [0.7, 1.4, 6.0], [1.5, 3.0, np.nan]])
>>> LUT3D.linear_table(domain=domain)
array([[[[-0.1, -0.2, -0.4],
         [-0.1, -0.2,  6. ]],

        [[-0.1,  1.4, -0.4],
         [-0.1,  1.4,  6. ]],

        [[-0.1,  3. , -0.4],
         [-0.1,  3. ,  6. ]]],


       [[[ 0.7, -0.2, -0.4],
         [ 0.7, -0.2,  6. ]],

        [[ 0.7,  1.4, -0.4],
         [ 0.7,  1.4,  6. ]],

        [[ 0.7,  3. , -0.4],
         [ 0.7,  3. ,  6. ]]],


       [[[ 1.5, -0.2, -0.4],
         [ 1.5, -0.2,  6. ]],

        [[ 1.5,  1.4, -0.4],
         [ 1.5,  1.4,  6. ]],

        [[ 1.5,  3. , -0.4],
         [ 1.5,  3. ,  6. ]]]])
invert(**kwargs: Any) LUT3D[source]#

Compute and return an inverse copy of the LUT.

Parameters:
  • interpolator – Interpolator class type or object to use as interpolating function.

  • query_size – Number of nearest neighbors to use for Shepard interpolation (inverse distance weighting). Default is 8, optimized for speed and quality. Higher values (16-32) may slightly improve smoothness but significantly increase computation time.

  • gamma

    Gradient smoothness parameter for Shepard interpolation. Default is 3.0 (optimized for smoothness). Controls the weight falloff rate in inverse distance weighting (\(w_i = 1/d_i^{1/gamma}\)). Higher gamma values produce smoother gradients.

    • Default (3.0): Optimal smoothness with minimal artifacts

    • Lower values (1.5-2.0): Sharper transitions, faster computation, may increase banding artifacts

    • Very low values (0.5-1.0): Maximum sharpness, more localized interpolation, higher banding risk

  • sigma

    Gaussian blur sigma for iterative adaptive smoothing. Default is 0.7. Smoothing is applied iteratively only to high-gradient regions (banding artifacts) identified using the percentile threshold, preserving quality in smooth regions.

    • Default (0.7): Optimal smoothing - reduces banding by ~38% (26 → 16 artifacts) while preserving corners

    • Higher values (0.8-0.9): More aggressive, may increase corner shift

    • Lower values (0.5-0.6): Gentler smoothing, better corner preservation

    • Set to 0.0 to disable adaptive smoothing entirely

    The iterative adaptive approach with gradient recomputation ensures clean LUTs remain unaffected while problematic regions receive targeted smoothing.

  • tau

    Percentile threshold for identifying high-gradient regions (0-1). Default is 0.75 (75th percentile). Higher values mean fewer regions are smoothed (more selective), lower values mean more regions are smoothed (more aggressive).

    • Default (0.75): Smooths top 25% of gradient regions

    • Higher values (0.85-0.95): Very selective, minimal smoothing

    • Lower values (0.50-0.65): More aggressive, smooths more regions

    Only used when sigma > 0.

  • iterations

    Number of iterative smoothing passes. Default is 10. Each iteration recomputes gradients and adapts smoothing to the evolving LUT state, providing better artifact reduction than a single strong blur.

    • Default (10): Optimal balance of quality and performance

    • Higher values (12-15): Slightly better artifact reduction, slower

    • Lower values (5-7): Faster, but fewer artifacts removed

    Only used when sigma > 0.

  • oversampling – Oversampling factor for building the KDTree. Default is 1.2. The optimal value is based on Jacobian analysis of the LUT transformation: the Jacobian matrix \(J = \partial(output)/\partial(input)\) measures local volume distortion. When \(|J| < 1\), the LUT compresses space, requiring higher sampling density for accurate inversion. The factor 1.2 captures approximately 80% of the theoretical accuracy benefit at 30% of the computational cost. Values between 1.0 (no oversampling) and 2.0 (diminishing returns) are supported.

  • size – Size of the inverse LUT. With the specified implementation, it is good practise to double the size of the inverse LUT to provide a smoother result. If size is not specified, \(2^{\sqrt{size_{LUT}} + 1} + 1\) will be used instead.

  • kwargs (Any)

Returns:

Inverse LUT class instance.

Return type:

colour.LUT3D

Examples

>>> LUT = LUT3D()
>>> print(LUT)
LUT3D - Unity 33
----------------

Dimensions : 3
Domain     : [[ 0.  0.  0.]
              [ 1.  1.  1.]]
Size       : (33, 33, 33, 3)
>>> print(LUT.invert())
LUT3D - Unity 33 - Inverse
--------------------------

Dimensions : 3
Domain     : [[ 0.  0.  0.]
              [ 1.  1.  1.]]
Size       : (108, 108, 108, 3)
apply(RGB: ArrayLike, **kwargs: Any) NDArrayFloat[source]#

Apply the LUT to the specified RGB colourspace array using the specified interpolation method.

Parameters:
  • RGB (ArrayLike) – RGB colourspace array to apply the LUT onto.

  • direction – Whether the LUT should be applied in the forward or inverse direction.

  • interpolator – Interpolator object to use as the interpolating function.

  • interpolator_kwargs – Arguments to use when calling the interpolating function.

  • query_size – Number of points to query in the KDTree, with their mean computed to produce a smoother result.

  • size – Size of the inverse LUT. With the specified implementation, it is recommended to double the size of the inverse LUT to provide a smoother result. If size is not specified, \(2^{\sqrt{size_{LUT}} + 1} + 1\) will be used instead.

  • kwargs (Any)

Returns:

Interpolated RGB colourspace array.

Return type:

numpy.ndarray

Examples

>>> LUT = LUT3D(LUT3D.linear_table() ** (1 / 2.2))
>>> RGB = np.array([0.18, 0.18, 0.18])
>>> LUT.apply(RGB)
array([ 0.4583277...,  0.4583277...,  0.4583277...])
>>> LUT.apply(LUT.apply(RGB), direction="Inverse")
...
array([ 0.1799897...,  0.1796077...,  0.1795868...])
>>> from colour.algebra import spow
>>> domain = np.array(
...     [
...         [-0.1, -0.2, -0.4],
...         [0.3, 1.4, 6.0],
...         [0.7, 3.0, np.nan],
...         [1.1, np.nan, np.nan],
...         [1.5, np.nan, np.nan],
...     ]
... )
>>> table = spow(LUT3D.linear_table(domain=domain), 1 / 2.2)
>>> LUT = LUT3D(table, domain=domain)
>>> RGB = np.array([0.18, 0.18, 0.18])
>>> LUT.apply(RGB)
array([ 0.2996370..., -0.0901332..., -0.3949770...])