arXiv | Project Page | LUT editor 🎨
Danna Xue, David Serrano-Lozano, Shaolin Su, and Javier Vazquez-Corral
Computer Vision Center, Universitat Autònoma de Barcelona
Gaussian LUT (GLUT) is a compact, continuous color representation based on learnable 3D Gaussian primitives. It avoids fixed-resolution grids while providing high accuracy, interpretability, and direct local editing.
glut_video.mp4
| Script | What it does |
|---|---|
train_glut.py |
GLUT – fit one LUT (a single input/target hald‑image pair). |
train_cglut.py |
CGLUT – fit many LUTs with one conditional model (a learned per‑LUT embedding drives an MLP that generates the Gaussian parameters). |
blend_lut.py |
Interpolate two LUTs on one image with a trained CGLUT (interpolate the two condition embeddings). |
interactive_glut_editor.py |
GUI to locally edit a fitted GLUT by modifying the top‑k Gaussians. |
-
Python ≥ 3.10, PyTorch ≥ 2.1 with CUDA (tested with 2.x + CUDA 12.6 on an RTX 4090).
-
Python packages:
pip install torch torchvision numpy opencv-python pillow matplotlib scipy \ colour-science kornia lpips
train_glut.py --cuda_eval and CGLUT.forward_unified(..., use_cuda=True) run the
forward pass through a hand‑written CUDA kernel. Build it once:
cd glut_cuda
python setup.py build_ext --inplaceRequires nvcc (CUDA toolkit). Edit -arch=sm_89 in glut_cuda/setup.py to match
your GPU (e.g. sm_89 = RTX 40‑series, sm_86 = RTX 30‑series).
If import glut_cuda fails with libc10.so: cannot open shared object file,
either import torch first (the training scripts already do) or set
export LD_LIBRARY_PATH=$(python -c 'import torch,os;print(os.path.dirname(torch.__file__))')/lib.
Training always uses PyTorch autograd; the kernel is inference‑only.
Everything is trained on hald images: a 2D image whose pixels enumerate every
RGB value of an identity 3D LUT. Applying a real LUT to that identity image gives
an (input, target) pair.
dataset/
|- 📁 cube_files/ # .cube files
|- |- 📁 7luts/
|- |- 📁 75luts/
| | |- 📘 LUT01.cube
| | |- 📘 LUT02.cube
| | |- ...
| | |- 📘 LUT75.cube
|- |- 📁 225luts/
|- |- ...
|- 📁 hald_images/
|- |- 📁 7luts_train/ # training set: one folder, many LUTs
| | |- Original_Image.png # identity hald (the model input)
| | |- 🖼️ LUT01.png # target = LUT01 applied to Original_Image
| | |- 🖼️ LUT02.png
| | |- ...
|- |- 📁 7luts_test/ # same LUT names, held-out hald resolution / crop
|- |- 📁 75luts_train/ # 75-LUT set used for the shipped checkpoint
|- |- 📁 75luts_test/
|- |- 📁 ...
Pretrained checkpoints (pretrained_models/) and .cube files (dataset/cube_files/)
are available here:
Models & .cube files
The easiest way is the end-to-end script, which generates the identity hald
image(s), applies every .cube file in a directory to them, and lays out the
result under dataset/hald_images/ in the folder structure above:
# train/test split (two hald images: main + held-out complement)
./data/prepare_hald_dataset.sh --lut_dir path/to/cube_files --step 2
# -> dataset/hald_images/<cube_dir_name>_train/
# -> dataset/hald_images/<cube_dir_name>_test/
# single full-coverage set (no train/test split)
./data/prepare_hald_dataset.sh --lut_dir path/to/cube_files --step 1
# -> dataset/hald_images/<cube_dir_name>_fullsize/To render a directory of .cube LUTs onto an existing hald image manually
(e.g. one you already have, or to control the output folder yourself), use
apply_cube.py directly — it writes one PNG per LUT plus Original_Image.png:
python data/apply_cube.py \
--lut_dir path/to/cube_files \
--hald_image path/to/hald_img.png \
--out_dir dataset/hald_images/my_luts_trainCUDA_VISIBLE_DEVICES=0 python train_glut.py \
--train_input dataset/hald_images/train/Original_Image.png \
--train_target dataset/hald_images/train/LUT02_After_Effects_LUTs_Blue_Shine.png \
--test_input dataset/hald_images/test/Original_Image.png \
--test_target dataset/hald_images/test/LUT02_After_Effects_LUTs_Blue_Shine.png \
--num_gaussians 32 \ # Gaussians per LUT.
--residual CUDA_VISIBLE_DEVICES=0 python train_cglut.py \
--lut_data ./dataset/hald_images/75lut_train \
--lut_data_test ./dataset/hald_images/75lut_test \
--num_gaussians 32 \ # Gaussians per LUT.
--num_conditions 64 \ # dimensionality of the LUT condition embedding.
--num_neurons 128 \
--shared_param 'all'| Flag | Meaning |
|---|---|
--num_neurons |
width of the encoder / parameter‑head MLPs. 128 for Large (L), 64 for Small (S). |
--shared_param |
which Gaussian parameter groups are shared across LUTs vs. generated per LUT: color_opacity (share positions and covariance - 'SharedGeo' setting), all (nothing shared - 'Full' setting). Colour transforms are always per‑LUT. |
The residual connection is always on. The model exposes:
forward(rgb, condition_idx)— per‑pixel condition, used for training/eval.forward_unified(rgb, condition_idx, use_cuda=False)— one condition for the whole batch: run the parameter generator once, reuse it for every pixel (much faster for full‑image inference).use_cuda=Trueroutes the blend through the compiled kernel.
Blend LUT A and LUT B at weight alpha by interpolating their learned
condition embeddings in a trained ConditionalGLUT
(emb = (1-alpha)·emb_A + alpha·emb_B), sweeping alpha from 0 to 1 for one
image. The model architecture is read from the checkpoint.
python blend_lut.py \
--image path/to/photo.png \
--lut dataset/cube_files/75lut \
--pretrained_model pretrained_models/cglut/cglut_g32_e64_7styles_..._Full.pth \
--lut_a 0 \
--lut_b 3 \
--n_alpha 6 \
--save_dir results/blendinteractive_glut_editor.py is a Dash web app for editing a trained GLUT by
picking an input color on an exemplar image and dragging it to a desired
output color — no retraining involved.
python interactive_glut_editor.py
Then open http://127.0.0.1:8050 in a browser.
Workflow:
- Upload an image via the toolbar button (or pass
--imageat launch). - Click a pixel on the original image → sets the source color.
- Pick the desired output color with the color picker.
- Tune the
KandStrengthsliders. - Press Apply Edit → the GLUT is updated in place, and the edited image, 3D cube view, and delta badge refresh instantly.
- Press Download All to get the original / default / edited images as a ZIP.
- Press Save Model to write the edited GLUT checkpoint.
- Press Reset to restore the original parameters.
Hope you like it 🤗
If you find this work interesting or you use it, don't forget to cite our work:
@article{xue2026glut,
title={GLUT: 3D Gaussian Lookup Table for Continuous Color Transformation},
author={Xue, Danna and Serrano-Lozano, David and Su, Shaolin and Vazquez-Corral, Javier},
journal={arXiv preprint arXiv:2605.19889},
year={2026}
}Thanks to the NILUT project for its code and data.