Convert 3D LUT to hald image

Discuss digital image processing techniques and algorithms. We encourage its application to ImageMagick but you can discuss any software solutions here.
User avatar
fmw42
Posts: 25135
Joined: 2007-07-02T17:14:51-07:00
Authentication code: 1152
Location: Sunnyvale, California, USA

Re: Convert 3D LUT to hald image

Post by fmw42 » 2018-12-17T13:43:17-07:00

IM can read the CUBE LUT and convert to an image. But that image is not a HALD image. We are looking for help to apply the CUBE LUT to process an image or to convert the CUBE LUT into a HALD image.

B4adle7
Posts: 11
Joined: 2018-06-10T10:25:40-07:00
Authentication code: 1152

Re: Convert 3D LUT to hald image

Post by B4adle7 » 2018-12-18T22:10:02-07:00

Perfect.. thanks for the input.
I've had a chance to do some further work on it, and yes confirmed on the cubed size for the HALD.
e.g. hald:4 is a resolution of 4**3 x 4**3

I do have a function created now that creates a HALD from a .cube with the option to tell it what HALD level you desire.
I will do a little research and verify the rules for posting code. fmw42 had asked for some code and will post the python function in case it might be useful or permitted.

Due to the process of needing to write a chunk of pixels at a time due to command length limits, the conversion can take several minutes.
Doing comparison on expected results it is 99.99999% accurate, which is more than enough. But will study the process some more in case there is a tweak that could be had.

The python function is aprox 160 lines, so want to research first if that is against the rules to post directly here that many lines.
Thanks everyone for their input.

User avatar
fmw42
Posts: 25135
Joined: 2007-07-02T17:14:51-07:00
Authentication code: 1152
Location: Sunnyvale, California, USA

Re: Convert 3D LUT to hald image

Post by fmw42 » 2018-12-18T22:33:21-07:00

The python function is aprox 160 lines, so want to research first if that is against the rules to post directly here that many lines.
Thanks everyone for their input.
It is not too much text to post here.

Or you can send directly via email -- see form at https://imagemagick.org/discourse-serve ... ntactadmin

We appreciate any help you can give on this activity.

User avatar
magick
Site Admin
Posts: 10995
Joined: 2003-05-31T11:32:55-07:00

Re: Convert 3D LUT to hald image

Post by magick » 2018-12-19T10:20:19-07:00

You can post the python script here or email it to snippets @ imagemagick dot org. You need to include a license, either public domain, BSD, or the ImageMagick license so we can adapt it and use it within the ImageMagick source distribution.

B4adle7
Posts: 11
Joined: 2018-06-10T10:25:40-07:00
Authentication code: 1152

Re: Convert 3D LUT to hald image

Post by B4adle7 » 2018-12-19T17:05:51-07:00

Here is the Python function for converting a .cube to a HALD image.
Note: It uses a subprocess.call to execute a standard "magick" command that the function builds and executes. The function uses a personal "BASE" module that I use to identify which OS it may be executing on and gives the appropriate "magick" command. In other words, you can replace that directory with the desired magick or magic path as desired.

Note: it loads in the .cube data into a 3D array, and then does a linear interpolate depending upon the desired HALD level.

Note: the "prog" reference can be ignored. It is for in case it is being called from a QT/PySide script with a progressBar widget if desired.

Note: criticism encouraged

Code: Select all

import sys
import re
import subprocess
import traceback

def cube_to_hald(cube_file, hald_file=None, hald_size=7, prog=None):
    """
    Turn that Cube into a HALD file
    Do one chunk at a time.
    If no hald_file given, create it next to the .cube.
    If title in the .cube, name the HALD after that.
    Otherwise the file given.
    """
    if not cube_file:
        return None

    lut_types = ('LUT_3D_SIZE', 'LUT_1D_SIZE')
    use_title = False
    title = None
    if not hald_file:
        cube, ext = os.path.splitext(cube_file)
        hald_file = '{0}.exr'.format(cube)
        use_title = True

    if sys.platform == 'win32':
        cube_file = cube_file.replace('/', '\\')
        hald_file = hald_file.replace('/', '\\')
    else:
        cube_file = cube_file.replace('\\', '/')
        hald_file = hald_file.replace('\\', '/')

    if not os.path.isfile(cube_file):
        return None
    fileID = open(cube_file, 'r')
    dump = fileID.readlines()
    fileID.close()

    dimension = None
    out_data = [[[[0.0] * 3 for x in range(hald_size**2)] for y in range(hald_size**2)] for z in range(hald_size**2)]
    r = g = b = 0
    for line in dump:
        each = line.strip()
        if not each:
            continue
        if each.startswith('#'):
            continue
        if dimension:
            buf = each.split(' ')
            data[b][g][r] = (float(buf[0]), float(buf[1]), float(buf[2]))
            r += 1
            if r >= dimension:
                r = 0
                g += 1
                if g >= dimension:
                    g = 0
                    b += 1
        if each.startswith('TITLE '):
            title = each[6:].replace(r'"', '')
        if each.startswith(lut_types):
            buf = each.split(' ')
            dimension = int(buf[1])
            data = [[[[0.0] * 3 for x in range(dimension)] for y in range(dimension)] for z in range(dimension)]
            continue

    #   Update hald filename and create directory accordingly
    hald_dir = os.path.dirname(hald_file)
    if use_title and title:
        hald_file = '{0}/{0}.exr'.format(hald_dir, title)
        if sys.platform == 'win32':
            hald_file = hald_file.replace('/', '\\')
    if not os.path.isdir(hald_dir):
        os.makedirs(hald_dir)

    #   Data loaded, build the HALD
    pVal = 0
    if prog:
        fmt = 'Interpolating HALD file (%v of %m)'
        prog.setFormat(fmt)
        prog.setRange(0, int(hald_size ** 2))
    else:
        sys.stdout.write('\n')

    length = dimension - 1
    factor = 1.0 / ((hald_size ** 2) - 1)
    for b in range(hald_size ** 2):
        for g in range(hald_size ** 2):
            for r in range(hald_size ** 2):
                r_position = (r * factor) * length
                r_factor, r_index = math.modf(r_position)
                r_index = int(r_index)
                if r_index == length:
                    r_next = r_index
                else:
                    r_next = r_index + 1

                g_position = (g * factor) * length
                g_factor, g_index = math.modf(g_position)
                g_index = int(g_index)
                if g_index == length:
                    g_next = g_index
                else:
                    g_next = g_index + 1

                b_position = (b * factor) * length
                b_factor, b_index = math.modf(b_position)
                b_index = int(b_index)
                if b_index == length:
                    b_next = b_index
                else:
                    b_next = b_index + 1

                out_data[b][g][r] = (data[b_index][g_index][r_index][0] + (data[b_index][g_index][r_next][0] - data[b_index][g_index][r_index][0]) * r_factor,
                                     data[b_index][g_index][r_index][1] + (data[b_index][g_next][r_index][1] - data[b_index][g_index][r_index][1]) * g_factor,
                                     data[b_index][g_index][r_index][2] + (data[b_next][g_index][r_index][2] - data[b_index][g_index][r_index][2]) * b_factor)
        if prog:
            pVal += 1
            prog.setValue(pVal)
        else:
            sys.stdout.write('.')

    #   Now create the results as a HALD image file.
    cmd = [r'"{0}" convert'.format(BASE.APP_PATHS['MAGICK'])]
    cmd.append('-size {0}x{0} xc:#000000000000 -depth 16'.format(str(hald_size ** 3)))
    cmd.append(r'"{0}"'.format(hald_file))
    try:
        subprocess.call(' '.join(cmd), shell=True)
    except Exception as err:
        traceback.print_exc()
        return None

    pVal = 0
    if prog:
        fmt = 'Building HALD file (%v of %m)'
        prog.setFormat(fmt)
        prog.setRange(0, int(hald_size ** 2))
    else:
        sys.stdout.write('\n')

    for b in range(hald_size ** 2):
        for g in range(hald_size ** 2):
            cmd = [r'"{0}" convert'.format(BASE.APP_PATHS['MAGICK'])]
            cmd.append(r'"{0}"'.format(hald_file))
            for r in range(hald_size ** 2):
                value = tuple([(hex(int(float(x) * 65535)).split('x')[-1].zfill(4)) for x in out_data[b][g][r]])
                x = (g % hald_size) * (hald_size ** 2) + r
                y = (b * hald_size) + ((g / hald_size) % (hald_size ** 2))
                cmd.append(r'-fill "#{0}{1}{2}"'.format(*value))
                cmd.append(r'-draw "color {0}, {1} point"'.format(x, y))
            cmd.append('-depth 16')
            cmd.append(r'"{0}"'.format(hald_file))
            try:
                subprocess.call(' '.join(cmd), shell=True)
            except Exception as err:
                traceback.print_exc()
                return None
        if prog:
            pVal += 1
            prog.setValue(pVal)
        else:
            sys.stdout.write('.')

    if prog:
        fmt = 'HALD Conversion Complete'
        prog.setFormat(fmt)
        prog.setValue(0)
    else:
        sys.stdout.write('\ncomplete\n')

    return hald_file

User avatar
fmw42
Posts: 25135
Joined: 2007-07-02T17:14:51-07:00
Authentication code: 1152
Location: Sunnyvale, California, USA

Re: Convert 3D LUT to hald image

Post by fmw42 » 2018-12-24T15:55:11-07:00

In IM 7.0.8.20 there is now a conversion form a CUBE LUT to a HALD Image. For example

Code: Select all

magick cube:FG_CineVibrant.cube[6] hald6.png
the [6] converts to a HALD:6 image. See

https://imagemagick.org/Usage/color_mods/#hald-clut

Post Reply