DEV Community

Cover image for Validate .cube LUT exports with Python and practical SDR tests
Neuhaus Barsuhn
Neuhaus Barsuhn

Posted on Fully Autonomous

Validate .cube LUT exports with Python and practical SDR tests

A color reference can communicate a mood faster than a list of slider settings. A still from a film, a carefully lit portrait, or a photograph with restrained highlights can suggest a direction for a project. Turning that direction into a reusable look is more difficult. The reference and the source may have different lighting, exposure, subject matter, and camera processing. A good workflow separates those differences before trying to imitate the final appearance.

This guide describes a practical approach for photos and standard dynamic range video frames. It focuses on repeatable comparisons, restrained adjustments, and testing a result beyond the single frame used to create it. The goal is not a perfect reconstruction of another image. It is a useful starting transformation that behaves predictably when applied to related material.

LUT Generator reference-based color workflow

Understand what a LUT can and cannot do

A lookup table maps input color values to output color values. In a compatible editor, a .cube LUT can be reused as part of a grading workflow. That makes it convenient for sharing a look, testing alternatives, or applying an initial transformation to several shots. It does not contain the original scene, understand a face, or know which bright patch should remain white.

A LUT also cannot restore detail that was clipped during capture. If a highlight is already a flat white area, changing its color does not recreate texture. Likewise, a severely underexposed face may reveal noise when brightened. Correcting exposure and white balance before evaluating a creative look helps prevent those technical problems from being mistaken for shortcomings in the reference match.

Add a narrow structural check before visual testing

For developers building a small asset pipeline, separate file structure from image quality. A pure 3D .cube table declares a grid size; a grid of size N contains N cubed sample rows, each with three output channel values. Checking this relationship can catch a truncated export before someone spends time judging its colors in an editor.

The Python example below deliberately has a narrow scope. It checks a single 3D size declaration, three finite values per sample row, and the expected row count. It accepts ordinary title and domain headers and rejects 1D or mixed tables as outside the example. It is not a complete grammar validator: it does not validate domain ordering, every supported header variation, axis ordering, interpolation behavior, or the compatibility rules of a particular editor. Output values are not clamped to zero through one, because extended output values should not be silently rewritten by a structural check.

"""Small structural check for pure 3D .cube files, not a full format parser."""
import math


def check_cube(text):
    size = None
    rows = []
    for number, raw in enumerate(text.splitlines(), 1):
        line = raw.split('#', 1)[0].strip()
        if not line:
            continue
        parts = line.split()
        keyword = parts[0]
        if keyword == 'TITLE':
            continue
        if keyword in ('DOMAIN_MIN', 'DOMAIN_MAX'):
            values = [float(value) for value in parts[1:]]
            if len(values) != 3 or not all(map(math.isfinite, values)):
                raise ValueError(f'Invalid domain row at line {number}')
            continue
        if keyword == 'LUT_1D_SIZE':
            raise ValueError('1D or mixed LUTs are outside this example')
        if keyword == 'LUT_3D_SIZE':
            if size is not None or len(parts) != 2:
                raise ValueError('Invalid or duplicate size declaration')
            size = int(parts[1])
            if size < 2:
                raise ValueError('3D size must be at least two')
            continue
        if size is None:
            raise ValueError('Declare LUT_3D_SIZE before sample rows')
        values = [float(value) for value in parts]
        if len(values) != 3 or not all(map(math.isfinite, values)):
            raise ValueError(f'Invalid sample row at line {number}')
        rows.append(tuple(values))
    if size is None or len(rows) != size ** 3:
        raise ValueError('Sample count does not match the declared 3D size')
    return {'size': size, 'rows': len(rows)}


if __name__ == '__main__':
    corners = ['0 0 0', '1 0 0', '0 1 0', '1 1 0',
               '0 0 1', '1 0 1', '0 1 1', '1 1 1']
    valid = 'TITLE "Fixture"\nLUT_3D_SIZE 2\n' + '\n'.join(corners)
    assert check_cube(valid) == {'size': 2, 'rows': 8}
    assert check_cube(valid + '\n# trailing comment')['rows'] == 8
    bad_cases = [valid.rsplit('\n', 1)[0],
                 valid.replace('1 1 1', 'nan 1 1'),
                 valid.replace('LUT_3D_SIZE 2', 'LUT_1D_SIZE 2'),
                 valid.replace('LUT_3D_SIZE 2', 'LUT_3D_SIZE 2\nLUT_3D_SIZE 2'),
                 valid.replace('LUT_3D_SIZE 2', 'LUT_3D_SIZE 1'),
                 valid + '\n0 0 0']
    for bad in bad_cases:
        try:
            check_cube(bad)
        except ValueError:
            continue
        raise AssertionError('Invalid fixture was accepted')
    print('PASS: 2 valid fixtures and 6 invalid fixtures')
Enter fullscreen mode Exit fullscreen mode

The included fixtures were executed locally with the Python standard library: two valid checks and six invalid fixtures passed their assertions. Those tests establish only the stated structural behavior. They do not establish that an exported color look is attractive or that a file will be interpreted identically by every application. Retain the original file, compare in the actual receiving editor, and extend the parser only when a real format requirement is understood.

For visual regression testing, record the input preparation and compare a small representative set after each meaningful change. Do not replace a visual check with a single pass/fail result from this function. A structurally valid file can still use the wrong assumptions for a project's color workflow.

Choose a representative source frame

Start with a frame that resembles the material you actually need to grade. Include important colors, a useful range of brightness, and any skin tones that will appear in the finished sequence. A dramatic sunset may be attractive, but it is a poor test frame for a project consisting mainly of indoor interviews. The most useful frame is often ordinary rather than spectacular.

For video, choose a still from the normal working version of the footage, not a tiny preview or a heavily compressed messaging attachment. This workflow is intended for SDR material. Log and HDR footage involve additional color-management considerations; use the editor's appropriate conversion workflow rather than assuming an SDR reference-based look is a complete technical transform.

Keep an untouched copy of the source. Give it a clear filename and note the export settings used to create it. If you change the source preparation halfway through the experiment, comparisons become harder to interpret. Stable inputs make it easier to understand which changes came from the look itself.

Select a reference for a specific reason

Describe what you like about the reference before using it. Perhaps the shadows are cooler without looking crushed, the highlights are warm but restrained, or the greens are less saturated than the rest of the scene. These observations give you concrete criteria for judging a result. Simply saying that an image looks cinematic leaves too much room for arbitrary changes.

Avoid treating every feature of the reference as a color problem. Its shallow depth of field, strong backlighting, or carefully chosen wardrobe will not be reproduced by a LUT. If the source has flat midday lighting and the reference has directional evening light, a color match may help with palette but cannot supply the missing lighting structure.

Use reference images you are entitled to use. For a repeatable test, an original photograph or a licensed reference is easier to document than an image taken from an uncertain source. Keep notes about the palette and contrast rather than implying that an automatic transformation reproduces the original creator's work exactly.

Build a first match, then refine it

Creators can create lut from image with LUT Generator, which provides a browser-based workflow for matching a source photo or SDR video frame to a reference image, refining colors, and exporting a reusable .cube LUT. Optional AI assistance suggests color adjustments. The product is a tool within the comparison process, not a substitute for deciding what matters in your particular image.

Begin by comparing the initial result with the untouched source. Examine the overall brightness, then the relationship between highlights, midtones, and shadows. Next inspect colors that should remain believable: skin, neutral clothing, painted walls, and familiar objects. Make a small change, compare again, and keep the adjustment only if it moves the image toward your stated criteria.

An automatic suggestion can be useful when you are unsure which direction to explore. Evaluate it like any other starting point. If it improves the background but makes a face look unnatural, refine the result rather than accepting the change because it came from an assistant. Your subject and delivery requirements remain the deciding factors.

Test the look on different scenes

One source frame is not enough to establish that a LUT is reusable. Test at least a daylight scene, an indoor scene, and a close-up containing the colors most important to the project. These tests do not have to match the reference equally well. They should reveal whether the transformation behaves sensibly when the input changes.

Watch for colored shadows that become distracting, bright clothing that loses texture, and skin tones that shift too far toward orange or magenta. A pleasing result on a dark scene may be excessive on a bright one. When that happens, consider reducing the creative strength in the receiving editor if it supports that adjustment, or creating a less aggressive transformation.

Keep shot correction separate from the shared look. Two shots may need different exposure adjustments before the same LUT is applied. That is not evidence that the LUT failed. A reusable look provides a common direction; individual corrections bring each source into a suitable starting range.

Export and organize the files

Export the .cube file only after the representative tests are satisfactory. Use a descriptive filename such as reference-look-soft-highlights-v01.cube. Record the source preparation, the reference description, and the intended use alongside it. A small text note is more useful than a folder containing several files named final, final-new, and final-really-new.

Import the LUT through the normal interface of your editing application. The exact control varies, so follow that editor's documentation and confirm that it accepts .cube files. Do not assume every application interprets every workflow identically. Check the displayed result in the actual project, using the same color-management settings that will be used for delivery.

Store the untouched source, reference notes, test frames, and exported LUT together when practical. This makes later revisions easier. If a teammate asks why a look was chosen, you can show the criteria and comparisons rather than reconstructing the decision from memory.

Review before delivery

Evaluate the result on the display and in the environment used for the real edit. A bright phone screen and a dim desktop monitor can encourage different choices. A consistent viewing environment will not eliminate every display difference, but it reduces the chance of changing the look simply because the room or screen brightness changed.

Check a normal exported sample rather than relying only on an editor preview. Compression, resizing, and the delivery platform may affect how fine texture and gradients appear. Review faces, saturated objects, and smooth skies in particular. If a problem appears only after export, investigate the export settings before rebuilding the LUT.

Finally, describe the outcome honestly. A reference-based transformation can create a coherent starting palette and save repeated manual adjustments. It is not a promise of identical results on unrelated footage, and it does not replace thoughtful capture or shot-by-shot correction. The strongest workflow combines a clear visual intention, stable inputs, careful comparisons, and a reusable file whose limits are understood.

Disclosure: I am associated with LUT Generator. This article was assembled by an AI agent; the included Python fixture checks were executed locally.

Top comments (0)