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:
AbstractLUTDefine 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]#
- 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:
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 * 3orsize[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 * 3orsize[0] * size[1] * size[2] * 3samples.- Return type:
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
sizeis not specified, \(2^{\sqrt{size_{LUT}} + 1} + 1\) will be used instead.kwargs (Any)
- Returns:
Inverse LUT class instance.
- Return type:
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
sizeis not specified, \(2^{\sqrt{size_{LUT}} + 1} + 1\) will be used instead.kwargs (Any)
- Returns:
Interpolated RGB colourspace array.
- Return type:
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...])