Python scripts used to generate ACES OCIO configurations
authorHaarm-Pieter Duiker <hpd1@duikerresearch.com>
Sun, 21 Dec 2014 19:08:25 +0000 (11:08 -0800)
committerHaarm-Pieter Duiker <hpd1@duikerresearch.com>
Sun, 21 Dec 2014 19:08:25 +0000 (11:08 -0800)
aces_1.0.0/python/create_aces_config.py [new file with mode: 0755]
aces_1.0.0/python/process.py [new file with mode: 0755]

diff --git a/aces_1.0.0/python/create_aces_config.py b/aces_1.0.0/python/create_aces_config.py
new file mode 100755 (executable)
index 0000000..dce012b
--- /dev/null
@@ -0,0 +1,2228 @@
+'''
+usage from python
+
+import sys
+sys.path.append( "/path/to/script" )
+import create_aces_config as cac
+acesReleaseCTLDir = "/path/to/github/checkout/releases/v0.7.1/transforms/ctl"
+configDir = "/path/to/config/dir"
+cac.createACESConfig(acesReleaseCTLDir, configDir, 1024, 33, True)
+
+usage from command line, from the directory with 'create_aces_config.py'
+python create_aces_config.py -a "/path/to/github/checkout/releases/v0.7.1/transforms/ctl" -c "/path/to/config/dir" --lutResolution1d 1024 --lutResolution3d 33 --keepTempImages
+
+
+
+build instructions for osx for needed packages.
+
+#opencolorio
+brew install -vd opencolorio --with-python
+
+#openimageio
+brew tap homebrew/science
+
+# optional installs
+brew install -vd libRaw
+brew install -vd OpenCV
+
+brew install -vd openimageio --with-python
+
+#ctl
+brew install -vd CTL
+
+#opencolorio - again.
+# this time, 'ociolutimage' will build because openimageio is installed
+brew uninstall -vd opencolorio
+brew install -vd opencolorio --with-python
+'''
+
+import sys
+import os
+import array
+import shutil
+import string
+import pprint
+import math
+import numpy
+
+import OpenImageIO as oiio
+import PyOpenColorIO as OCIO
+
+import process
+
+#
+# Utility functions
+#
+def setConfigDefaultRoles( config, 
+    color_picking="",
+    color_timing="",
+    compositing_log="",
+    data="",
+    default="",
+    matte_paint="",
+    reference="",
+    scene_linear="",
+    texture_paint=""):
+    
+    # Add Roles
+    if color_picking: config.setRole( OCIO.Constants.ROLE_COLOR_PICKING, color_picking )
+    if color_timing: config.setRole( OCIO.Constants.ROLE_COLOR_TIMING, color_timing )
+    if compositing_log: config.setRole( OCIO.Constants.ROLE_COMPOSITING_LOG, compositing_log )
+    if data: config.setRole( OCIO.Constants.ROLE_DATA, data )
+    if default: config.setRole( OCIO.Constants.ROLE_DEFAULT, default )
+    if matte_paint: config.setRole( OCIO.Constants.ROLE_MATTE_PAINT, matte_paint )
+    if reference: config.setRole( OCIO.Constants.ROLE_REFERENCE, reference )
+    if scene_linear: config.setRole( OCIO.Constants.ROLE_SCENE_LINEAR, scene_linear )
+    if texture_paint: config.setRole( OCIO.Constants.ROLE_TEXTURE_PAINT, texture_paint )
+    
+    
+
+# Write config to disk
+def writeConfig( config, configPath, sanityCheck=True ):
+    if sanityCheck:
+        try:
+            config.sanityCheck()
+        except Exception,e:
+            print e
+            print "Configuration was not written due to a failed Sanity Check"
+            return
+            #sys.exit()
+
+    fileHandle = open( configPath, mode='w' )
+    fileHandle.write( config.serialize() )
+    fileHandle.close()
+
+#
+# Functions used to generate LUTs using CTL transforms
+#
+def generate1dLUTImage(ramp1dPath, resolution=1024, minValue=0.0, maxValue=1.0):
+    #print( "Generate 1d LUT image - %s" % ramp1dPath)
+
+    # open image
+    format = os.path.splitext(ramp1dPath)[1]
+    ramp = oiio.ImageOutput.create(ramp1dPath)
+
+    # set image specs
+    spec = oiio.ImageSpec()
+    spec.set_format( oiio.FLOAT )
+    #spec.format.basetype = oiio.FLOAT
+    spec.width = resolution
+    spec.height = 1
+    spec.nchannels = 3
+
+    ramp.open (ramp1dPath, spec, oiio.Create)
+
+    data = array.array("f", "\0" * spec.width * spec.height * spec.nchannels * 4)
+    for i in range(resolution):
+        value = float(i)/(resolution-1) * (maxValue - minValue) + minValue
+        data[i*spec.nchannels +0] = value
+        data[i*spec.nchannels +1] = value
+        data[i*spec.nchannels +2] = value
+
+    ramp.write_image(spec.format, data)
+    ramp.close()
+
+# Credit to Alex Fry for the original single channel version of the spi1d writer
+def WriteSPI1D(filename, fromMin, fromMax, data, entries, channels):
+    f = file(filename,'w')
+    f.write("Version 1\n")
+    f.write("From %f %f\n" % (fromMin, fromMax))
+    f.write("Length %d\n" % entries)
+    f.write("Components %d\n" % (min(3, channels)) )
+    f.write("{\n")
+    for i in range(0, entries):
+        entry = ""
+        for j in range(0, min(3, channels)):
+            entry = "%s %s" % (entry, data[i*channels + j])
+        f.write("        %s\n" % entry)
+    f.write("}\n")
+    f.close()
+
+def generate1dLUTFromImage(ramp1dPath, outputPath=None, minValue=0.0, maxValue=1.0):
+    if outputPath == None:
+        outputPath = ramp1dPath + ".spi1d"
+
+    # open image
+    ramp = oiio.ImageInput.open( ramp1dPath )
+
+    # get image specs
+    spec = ramp.spec()
+    type = spec.format.basetype
+    width = spec.width
+    height = spec.height
+    channels = spec.nchannels
+
+    # get data
+    # Force data to be read as float. The Python API doesn't handle half-floats well yet.
+    type = oiio.FLOAT
+    data = ramp.read_image(type)
+
+    WriteSPI1D(outputPath, minValue, maxValue, data, width, channels)
+
+def generate3dLUTImage(ramp3dPath, resolution=32):
+    args = ["--generate", "--cubesize", str(resolution), "--maxwidth", str(resolution*resolution), "--output", ramp3dPath]
+    lutExtract = process.Process(description="generate a 3d LUT image", cmd="ociolutimage", args=args)
+    lutExtract.execute()    
+
+def generate3dLUTFromImage(ramp3dPath, outputPath=None, resolution=32):
+    if outputPath == None:
+        outputPath = ramp3dPath + ".spi3d"
+
+    args = ["--extract", "--cubesize", str(resolution), "--maxwidth", str(resolution*resolution), "--input", ramp3dPath, "--output", outputPath]
+    lutExtract = process.Process(description="extract a 3d LUT", cmd="ociolutimage", args=args)
+    lutExtract.execute()    
+
+def applyCTLToImage(inputImage, 
+    outputImage, 
+    ctlPaths=[], 
+    inputScale=1.0, 
+    outputScale=1.0, 
+    globalParams={},
+    acesCTLReleaseDir=None):
+    if len(ctlPaths) > 0:
+        ctlenv = os.environ
+        if acesCTLReleaseDir != None:
+            ctlModulePath = "%s/utilities" % acesCTLReleaseDir
+            ctlenv['CTL_MODULE_PATH'] = ctlModulePath
+
+        args = []
+        for ctl in ctlPaths:
+            args += ['-ctl', ctl]
+        args += ["-force"]
+        #args += ["-verbose"]
+        args += ["-input_scale", str(inputScale)]
+        args += ["-output_scale", str(outputScale)]
+        args += ["-global_param1", "aIn", "1.0"]
+        for key, value in globalParams.iteritems():
+            args += ["-global_param1", key, str(value)]
+        args += [inputImage]
+        args += [outputImage]
+
+        #print( "args : %s" % args )
+
+        ctlp = process.Process(description="a ctlrender process", cmd="ctlrender", args=args, env=ctlenv )
+
+        ctlp.execute()
+
+def convertBitDepth(inputImage, outputImage, depth):
+    args = [inputImage, "-d", depth, "-o", outputImage]
+    convert = process.Process(description="convert image bit depth", cmd="oiiotool", args=args)
+    convert.execute()    
+
+def generate1dLUTFromCTL(lutPath, 
+    ctlPaths, 
+    lutResolution=1024, 
+    identityLutBitDepth='half', 
+    inputScale=1.0, 
+    outputScale=1.0,
+    globalParams={},
+    cleanup=True,
+    acesCTLReleaseDir=None,
+    minValue=0.0,
+    maxValue=1.0):
+    #print( lutPath )
+    #print( ctlPaths )
+
+    lutPathBase = os.path.splitext(lutPath)[0]
+
+    identityLUTImageFloat = lutPathBase + ".float.tiff"
+    generate1dLUTImage(identityLUTImageFloat, lutResolution, minValue, maxValue)
+
+    if identityLutBitDepth != 'half':
+        identityLUTImage = lutPathBase + ".uint16.tiff"
+        convertBitDepth(identityLUTImageFloat, identityLUTImage, identityLutBitDepth)
+    else:
+        identityLUTImage = identityLUTImageFloat
+
+    transformedLUTImage = lutPathBase + ".transformed.exr"
+    applyCTLToImage(identityLUTImage, transformedLUTImage, ctlPaths, inputScale, outputScale, globalParams, acesCTLReleaseDir)
+
+    generate1dLUTFromImage(transformedLUTImage, lutPath, minValue, maxValue)
+
+    if cleanup:
+        os.remove(identityLUTImage)
+        if identityLUTImage != identityLUTImageFloat:
+            os.remove(identityLUTImageFloat)
+        os.remove(transformedLUTImage)
+
+def correctLUTImage(transformedLUTImage, correctedLUTImage, lutResolution):
+    # open image
+    transformed = oiio.ImageInput.open( transformedLUTImage )
+
+    # get image specs
+    transformedSpec = transformed.spec()
+    type = transformedSpec.format.basetype
+    width = transformedSpec.width
+    height = transformedSpec.height
+    channels = transformedSpec.nchannels
+
+    # rotate or not
+    if width != lutResolution * lutResolution or height != lutResolution:
+        print( "Correcting image as resolution is off. Found %d x %d. Expected %d x %d" % (width, height, lutResolution * lutResolution, lutResolution) )
+        print( "Generating %s" % correctedLUTImage)
+
+        #
+        # We're going to generate a new correct image
+        #
+
+        # Get the source data
+        # Force data to be read as float. The Python API doesn't handle half-floats well yet.
+        type = oiio.FLOAT
+        sourceData = transformed.read_image(type)
+
+        format = os.path.splitext(correctedLUTImage)[1]
+        correct = oiio.ImageOutput.create(correctedLUTImage)
+
+        # set image specs
+        correctSpec = oiio.ImageSpec()
+        correctSpec.set_format( oiio.FLOAT )
+        correctSpec.width = height
+        correctSpec.height = width
+        correctSpec.nchannels = channels
+
+        correct.open (correctedLUTImage, correctSpec, oiio.Create)
+
+        destData = array.array("f", "\0" * correctSpec.width * correctSpec.height * correctSpec.nchannels * 4)
+        for j in range(0, correctSpec.height):
+            for i in range(0, correctSpec.width):
+                for c in range(0, correctSpec.nchannels):
+                    #print( i, j, c )
+                    destData[correctSpec.nchannels*correctSpec.width*j + correctSpec.nchannels*i + c] = sourceData[correctSpec.nchannels*correctSpec.width*j + correctSpec.nchannels*i + c]
+
+        correct.write_image(correctSpec.format, destData)
+        correct.close()
+    else:
+        #shutil.copy(transformedLUTImage, correctedLUTImage)
+        correctedLUTImage = transformedLUTImage
+
+    transformed.close()
+
+    return correctedLUTImage
+
+def generate3dLUTFromCTL(lutPath, 
+    ctlPaths, 
+    lutResolution=64, 
+    identityLutBitDepth='half', 
+    inputScale=1.0,
+    outputScale=1.0, 
+    globalParams={},
+    cleanup=True,
+    acesCTLReleaseDir=None):
+    #print( lutPath )
+    #print( ctlPaths )
+
+    lutPathBase = os.path.splitext(lutPath)[0]
+
+    identityLUTImageFloat = lutPathBase + ".float.tiff"
+    generate3dLUTImage(identityLUTImageFloat, lutResolution)
+
+
+    if identityLutBitDepth != 'half':
+        identityLUTImage = lutPathBase + "." + identityLutBitDepth + ".tiff"
+        convertBitDepth(identityLUTImageFloat, identityLUTImage, identityLutBitDepth)
+    else:
+        identityLUTImage = identityLUTImageFloat
+
+    transformedLUTImage = lutPathBase + ".transformed.exr"
+    applyCTLToImage(identityLUTImage, transformedLUTImage, ctlPaths, inputScale, outputScale, globalParams, acesCTLReleaseDir)
+
+    correctedLUTImage = lutPathBase + ".correct.exr"
+    correctedLUTImage = correctLUTImage(transformedLUTImage, correctedLUTImage, lutResolution)    
+
+    generate3dLUTFromImage(correctedLUTImage, lutPath, lutResolution)
+
+    if cleanup:
+        os.remove(identityLUTImage)
+        if identityLUTImage != identityLUTImageFloat:
+            os.remove(identityLUTImageFloat)
+        os.remove(transformedLUTImage)
+        if correctedLUTImage != transformedLUTImage:
+            os.remove(correctedLUTImage)
+        #os.remove(correctedLUTImage)
+
+def generateOCIOTransform(transforms):
+    #print( "Generating transforms")
+
+    interpolationOptions = { 
+        'linear':OCIO.Constants.INTERP_LINEAR,
+        'nearest':OCIO.Constants.INTERP_NEAREST, 
+        'tetrahedral':OCIO.Constants.INTERP_TETRAHEDRAL 
+    }
+    directionOptions = { 
+        'forward':OCIO.Constants.TRANSFORM_DIR_FORWARD,
+        'inverse':OCIO.Constants.TRANSFORM_DIR_INVERSE 
+    }
+
+    ocioTransforms = []
+
+    for transform in transforms:
+        if transform['type'] == 'lutFile':
+
+            ocioTransform = OCIO.FileTransform( src=transform['path'],
+                interpolation=interpolationOptions[transform['interpolation']],
+                direction=directionOptions[transform['direction']] )
+
+            ocioTransforms.append(ocioTransform)
+        elif transform['type'] == 'matrix':
+            ocioTransform = OCIO.MatrixTransform()
+            # MatrixTransform member variables can't be initialized directly. Each must be set individually
+            ocioTransform.setMatrix( transform['matrix'] )
+
+            if 'offset' in transform:
+                ocioTransform.setOffset( transform['offset'] )
+            if 'direction' in transform:
+                ocioTransform.setDirection( directionOptions[transform['direction']] )
+
+            ocioTransforms.append(ocioTransform)
+        elif transform['type'] == 'exponent':
+            ocioTransform = OCIO.ExponentTransform()
+            ocioTransform.setValue( transform['value'] )
+
+            ocioTransforms.append(ocioTransform)
+        elif transform['type'] == 'log':
+            ocioTransform = OCIO.LogTransform(base=transform['base'],
+                direction=directionOptions[transform['direction']])
+
+            ocioTransforms.append(ocioTransform)
+        else:
+            print( "Ignoring unknown transform type : %s" % transform['type'] )
+
+    # Build a group transform if necessary
+    if len(ocioTransforms) > 1:
+        transformG = OCIO.GroupTransform()
+        for transform in ocioTransforms:
+            transformG.push_back( transform )
+        transform = transformG
+
+    # Or take the first transform from the list
+    else:
+        transform = ocioTransforms[0]
+
+    return transform
+
+def createConfig(configData, nuke=False):
+    # Create the config
+    config = OCIO.Config()
+    
+    #
+    # Set config wide values
+    #
+    config.setDescription( "An ACES config generated from python" )
+    config.setSearchPath( "luts" )
+    
+    #
+    # Define the reference color space
+    #
+    referenceData = configData['referenceColorSpace']
+    print( "Adding the reference color space : %s" % referenceData.name)
+
+    # Create a color space
+    reference = OCIO.ColorSpace( name=referenceData.name, 
+        bitDepth=referenceData.bitDepth, 
+        description=referenceData.description, 
+        equalityGroup=referenceData.equalityGroup, 
+        family=referenceData.family, 
+        isData=referenceData.isData, 
+        allocation=referenceData.allocationType, 
+        allocationVars=referenceData.allocationVars ) 
+
+    # Add to config
+    config.addColorSpace( reference )
+
+    #
+    # Create the rest of the color spaces
+    #
+    #sortedColorspaces = sorted(configData['colorSpaces'], key=lambda x: x.name)
+    #print( sortedColorspaces )
+    #for colorspace in sortedColorspaces:
+    for colorspace in sorted(configData['colorSpaces']):
+        print( "Creating new color space : %s" % colorspace.name)
+
+        ocioColorspace = OCIO.ColorSpace( name=colorspace.name, 
+            bitDepth=colorspace.bitDepth, 
+            description=colorspace.description, 
+            equalityGroup=colorspace.equalityGroup, 
+            family=colorspace.family, 
+            isData=colorspace.isData,
+            allocation=colorspace.allocationType, 
+            allocationVars=colorspace.allocationVars ) 
+
+        if colorspace.toReferenceTransforms != []:
+            print( "Generating To-Reference transforms")
+            ocioTransform = generateOCIOTransform(colorspace.toReferenceTransforms)
+            ocioColorspace.setTransform( ocioTransform, OCIO.Constants.COLORSPACE_DIR_TO_REFERENCE )
+
+        if colorspace.fromReferenceTransforms != []:
+            print( "Generating From-Reference transforms")
+            ocioTransform = generateOCIOTransform(colorspace.fromReferenceTransforms)
+            ocioColorspace.setTransform( ocioTransform, OCIO.Constants.COLORSPACE_DIR_FROM_REFERENCE )
+
+        config.addColorSpace(ocioColorspace)
+
+        print( "" )
+
+    #
+    # Define the views and displays
+    #
+    displays = []
+    views = []
+
+    # Generic display and view setup
+    if not nuke:
+        for display, viewList in configData['displays'].iteritems():
+            for viewName, colorspace in viewList.iteritems():
+                config.addDisplay( display, viewName, colorspace.name )
+                if not (viewName in views):
+                    views.append(viewName)
+            displays.append(display)
+    # A Nuke specific set of views and displays
+    #
+    # XXX
+    # A few names: Output Transform, ACES, ACEScc, are hard-coded here. Would be better to automate
+    #
+    else:
+        for display, viewList in configData['displays'].iteritems():
+            for viewName, colorspace in viewList.iteritems():
+                if( viewName == 'Output Transform'):
+                    viewName = 'View'
+                    config.addDisplay( display, viewName, colorspace.name )
+                    if not (viewName in views):
+                        views.append(viewName)
+            displays.append(display)
+
+        config.addDisplay( 'linear', 'View', 'ACES2065-1' )
+        displays.append('linear')
+        config.addDisplay( 'log', 'View', 'ACEScc' )
+        displays.append('log')
+
+    # Set active displays and views
+    config.setActiveDisplays( ','.join(sorted(displays)) )
+    config.setActiveViews( ','.join(views) )
+
+    #
+    # Need to generalize this at some point
+    #
+
+    # Add Default Roles
+    setConfigDefaultRoles( config, 
+        color_picking=reference.getName(),
+        color_timing=reference.getName(),
+        compositing_log=reference.getName(),
+        data=reference.getName(),
+        default=reference.getName(),
+        matte_paint=reference.getName(),
+        reference=reference.getName(),
+        scene_linear=reference.getName(),
+        texture_paint=reference.getName() )
+
+    # Check to make sure we didn't screw something up
+    config.sanityCheck()
+
+    return config
+
+#
+# Functions to generate color space definitions and LUTs for transforms for a specific ACES release
+#
+class ColorSpace:
+    "A container for data needed to define an OCIO 'Color Space' "
+
+    def __init__(self, 
+        name, 
+        description=None, 
+        bitDepth=OCIO.Constants.BIT_DEPTH_F32,
+        equalityGroup=None,
+        family=None,
+        isData=False,
+        toReferenceTransforms=[],
+        fromReferenceTransforms=[],
+        allocationType=OCIO.Constants.ALLOCATION_UNIFORM,
+        allocationVars=[0.0, 1.0]):
+        "Initialize the standard class variables"
+        self.name = name
+        self.bitDepth=bitDepth
+        self.description = description
+        self.equalityGroup=equalityGroup
+        self.family=family 
+        self.isData=isData
+        self.toReferenceTransforms=toReferenceTransforms
+        self.fromReferenceTransforms=fromReferenceTransforms
+        self.allocationType=allocationType
+        self.allocationVars=allocationVars
+
+# Create a 4x4 matrix (list) based on a 3x3 matrix (list) input
+def mat44FromMat33(mat33):
+    return [mat33[0], mat33[1], mat33[2], 0.0, 
+            mat33[3], mat33[4], mat33[5], 0.0, 
+            mat33[6], mat33[7], mat33[8], 0.0, 
+            0,0,0,1.0]
+
+
+# Output is a list of colorspaces and transforms that convert between those
+# colorspaces and reference color space, ACES
+def generateLUTs(odtInfo, lmtInfo, shaperName, acesCTLReleaseDir, lutDir, lutResolution1d=4096, lutResolution3d=64, cleanup=True):
+    print( "generateLUTs - begin" )
+    configData = {}
+
+    #
+    # Define the reference color space
+    #
+    ACES = ColorSpace('ACES2065-1')
+    ACES.description = "The Academy Color Encoding System reference color space"
+    ACES.equalityGroup = ''
+    ACES.family = 'ACES'
+    ACES.isData=False
+    ACES.allocationType=OCIO.Constants.ALLOCATION_LG2
+    ACES.allocationVars=[-15, 6]
+
+    configData['referenceColorSpace'] = ACES
+
+    #
+    # Define the displays
+    #
+    configData['displays'] = {}
+
+    #
+    # Define the other color spaces
+    #
+    configData['colorSpaces'] = []
+
+    # Matrix converting ACES AP1 primaries to AP0
+    acesAP1toAP0 = [0.6954522414, 0.1406786965, 0.1638690622,
+                    0.0447945634, 0.8596711185, 0.0955343182,
+                    -0.0055258826, 0.0040252103, 1.0015006723]
+
+    #
+    # ACEScc
+    #
+    def createACEScc(name='ACEScc', minValue=0.0, maxValue=1.0, inputScale=1.0):
+        cs = ColorSpace(name)
+        cs.description = "The %s color space" % name
+        cs.equalityGroup = ''
+        cs.family = 'ACES'
+        cs.isData=False
+
+        ctls = [
+            '%s/ACEScc/ACEScsc.ACEScc_to_ACES.a1.0.0.ctl' % acesCTLReleaseDir,
+            # This transform gets back to the AP1 primaries
+            # Useful to the 1d LUT is only covering the transfer function
+            # The primaries switch is covered by the matrix below
+            '%s/ACEScg/ACEScsc.ACES_to_ACEScg.a1.0.0.ctl' % acesCTLReleaseDir
+        ]
+        lut = "%s_to_ACES.spi1d" % name
+        generate1dLUTFromCTL( lutDir + "/" + lut, 
+            ctls, 
+            lutResolution1d, 
+            'float', 
+            inputScale,
+            1.0, 
+            {},
+            cleanup, 
+            acesCTLReleaseDir,
+            minValue,
+            maxValue)
+
+        cs.toReferenceTransforms = []
+        cs.toReferenceTransforms.append( {
+            'type':'lutFile', 
+            'path':lut, 
+            'interpolation':'linear', 
+            'direction':'forward'
+        } )
+
+        # AP1 primaries to AP0 primaries
+        cs.toReferenceTransforms.append( {
+            'type':'matrix',
+            'matrix':mat44FromMat33(acesAP1toAP0),
+            'direction':'forward'
+        })
+
+        cs.fromReferenceTransforms = []
+        return cs
+
+    ACEScc = createACEScc()
+    configData['colorSpaces'].append(ACEScc)
+
+    #
+    # ACESproxy
+    #
+    def createACESProxy(name='ACESproxy'):
+        cs = ColorSpace(name)
+        cs.description = "The %s color space" % name
+        cs.equalityGroup = ''
+        cs.family = 'ACES'
+        cs.isData=False
+
+        ctls = [
+            '%s/ACESproxy/ACEScsc.ACESproxy10i_to_ACES.a1.0.0.ctl' % acesCTLReleaseDir,
+            # This transform gets back to the AP1 primaries
+            # Useful to the 1d LUT is only covering the transfer function
+            # The primaries switch is covered by the matrix below
+            '%s/ACEScg/ACEScsc.ACES_to_ACEScg.a1.0.0.ctl' % acesCTLReleaseDir
+        ]
+        lut = "%s_to_aces.spi1d" % name
+        generate1dLUTFromCTL( lutDir + "/" + lut, 
+            ctls, 
+            lutResolution1d, 
+            'uint16', 
+            64.0,
+            1.0, 
+            {},
+            cleanup, 
+            acesCTLReleaseDir )
+
+        cs.toReferenceTransforms = []
+        cs.toReferenceTransforms.append( {
+            'type':'lutFile', 
+            'path':lut, 
+            'interpolation':'linear', 
+            'direction':'forward'
+        } )
+
+        # AP1 primaries to AP0 primaries
+        cs.toReferenceTransforms.append( {
+            'type':'matrix',
+            'matrix':mat44FromMat33(acesAP1toAP0),
+            'direction':'forward'
+        })
+
+
+        cs.fromReferenceTransforms = []
+        return cs
+
+    ACESproxy = createACESProxy()
+    configData['colorSpaces'].append(ACESproxy)
+
+    #
+    # ACEScg
+    #
+    def createACEScg(name='ACEScg'):
+        cs = ColorSpace(name)
+        cs.description = "The %s color space" % name
+        cs.equalityGroup = ''
+        cs.family = 'ACES'
+        cs.isData=False
+
+        cs.toReferenceTransforms = []
+
+        # AP1 primaries to AP0 primaries
+        cs.toReferenceTransforms.append( {
+            'type':'matrix',
+            'matrix':mat44FromMat33(acesAP1toAP0),
+            'direction':'forward'
+        })
+
+        cs.fromReferenceTransforms = []
+        return cs
+
+    ACEScg = createACEScg()
+    configData['colorSpaces'].append(ACEScg)
+
+    #
+    # ADX
+    #
+    def createADX(bitdepth=10, name='ADX'):
+        name = "%s%s" % (name, bitdepth)
+        cs = ColorSpace(name)
+        cs.description = "%s color space - used for film scans" % name
+        cs.equalityGroup = ''
+        cs.family = 'ADX'
+        cs.isData=False
+
+        if bitdepth == 10:
+            cs.bitDepth = bitDepth=OCIO.Constants.BIT_DEPTH_UINT10
+            adx_to_cdd = [1023.0/500.0, 0.0, 0.0, 0.0,
+                        0.0, 1023.0/500.0, 0.0, 0.0,
+                        0.0, 0.0, 1023.0/500.0, 0.0,
+                        0.0, 0.0, 0.0, 1.0]
+            offset = [-95.0/500.0, -95.0/500.0, -95.0/500.0, 0.0]
+        elif bitdepth == 16:
+            cs.bitDepth = bitDepth=OCIO.Constants.BIT_DEPTH_UINT16
+            adx_to_cdd = [65535.0/8000.0, 0.0, 0.0, 0.0,
+                        0.0, 65535.0/8000.0, 0.0, 0.0,
+                        0.0, 0.0, 65535.0/8000.0, 0.0,
+                        0.0, 0.0, 0.0, 1.0]
+            offset = [-1520.0/8000.0, -1520.0/8000.0, -1520.0/8000.0, 0.0]
+
+        cs.toReferenceTransforms = []
+
+        # Convert from ADX to Channel-Dependent Density
+        cs.toReferenceTransforms.append( {
+            'type':'matrix',
+            'matrix':adx_to_cdd,
+            'offset':offset,
+            'direction':'forward'
+        })
+
+        # Convert from Channel-Dependent Density to Channel-Independent Density
+        cs.toReferenceTransforms.append( {
+            'type':'matrix',
+            'matrix':[0.75573, 0.22197, 0.02230, 0,
+                        0.05901, 0.96928, -0.02829, 0,
+                        0.16134, 0.07406, 0.76460, 0,
+                        0.0, 0.0, 0.0, 1.0],
+            'direction':'forward'
+        })
+
+        # Copied from Alex Fry's adx_cid_to_rle.py
+        def createCIDtoRLELUT():
+            def interpolate1D(x, xp, fp):
+                return numpy.interp(x, xp, fp)
+
+            LUT_1D_xp = [-0.190000000000000, 
+                          0.010000000000000,
+                          0.028000000000000,
+                          0.054000000000000,
+                          0.095000000000000,
+                          0.145000000000000,
+                          0.220000000000000,
+                          0.300000000000000,
+                          0.400000000000000,
+                          0.500000000000000,
+                          0.600000000000000]
+
+            LUT_1D_fp = [-6.000000000000000, 
+                         -2.721718645000000,
+                         -2.521718645000000,
+                         -2.321718645000000,
+                         -2.121718645000000,
+                         -1.921718645000000,
+                         -1.721718645000000,
+                         -1.521718645000000,
+                         -1.321718645000000,
+                         -1.121718645000000,
+                         -0.926545676714876]
+
+            REF_PT = (7120.0 - 1520.0) / 8000.0 * (100.0 / 55.0) - math.log(0.18, 10.0)
+
+            def cid_to_rle(x):
+                if x <= 0.6:
+                    return interpolate1D(x, LUT_1D_xp, LUT_1D_fp)
+                return (100.0 / 55.0) * x - REF_PT
+
+            def Fit(value, fromMin, fromMax, toMin, toMax):
+                if fromMin == fromMax:
+                    raise ValueError("fromMin == fromMax")
+                return (value - fromMin) / (fromMax - fromMin) * (toMax - toMin) + toMin
+
+            NUM_SAMPLES = 2**12
+            RANGE = (-0.19, 3.0)
+            data = []
+            for i in xrange(NUM_SAMPLES):
+                x = i/(NUM_SAMPLES-1.0)
+                x = Fit(x, 0.0, 1.0, RANGE[0], RANGE[1])
+                data.append(cid_to_rle(x))
+
+            lut = 'ADX_CID_to_RLE.spi1d'
+            WriteSPI1D(lutDir + "/" + lut, RANGE[0], RANGE[1], data, NUM_SAMPLES, 1)
+
+            return lut
+
+        # Convert Channel Independent Density values to Relative Log Exposure values
+        lut = createCIDtoRLELUT()
+        cs.toReferenceTransforms.append( {
+            'type':'lutFile', 
+            'path':lut, 
+            'interpolation':'linear', 
+            'direction':'forward'
+        })
+
+        # Convert Relative Log Exposure values to Relative Exposure values
+        cs.toReferenceTransforms.append( {
+            'type':'log', 
+            'base':10, 
+            'direction':'inverse'
+        })
+
+        # Convert Relative Exposure values to ACES values
+        cs.toReferenceTransforms.append( {
+            'type':'matrix',
+            'matrix':[0.72286, 0.12630, 0.15084, 0,
+                        0.11923, 0.76418, 0.11659, 0,
+                        0.01427, 0.08213, 0.90359, 0,
+                        0.0, 0.0, 0.0, 1.0],
+            'direction':'forward'
+        })
+
+        cs.fromReferenceTransforms = []
+        return cs
+
+    ADX10 = createADX(bitdepth=10)
+    configData['colorSpaces'].append(ADX10)
+
+    ADX16 = createADX(bitdepth=16)
+    configData['colorSpaces'].append(ADX16)
+
+
+    #
+    # REDlogFilm to ACES
+    #
+    def createREDlogFilm(gamut, transferFunction, name='REDlogFilm'):
+        name = "%s - %s" % (transferFunction, gamut)
+        if transferFunction == "":
+            name = "Linear - %s" % gamut
+        if gamut == "":
+            name = "%s" % transferFunction
+
+        cs = ColorSpace(name)
+        cs.description = name
+        cs.equalityGroup = ''
+        cs.family = 'RED'
+        cs.isData=False
+
+        def cineonToLinear(codeValue):
+            nGamma = 0.6
+            blackPoint = 95.0
+            whitePoint = 685.0
+            codeValueToDensity = 0.002
+
+            blackLinear = pow(10.0, (blackPoint - whitePoint) * (codeValueToDensity / nGamma))
+            codeLinear = pow(10.0, (codeValue - whitePoint) * (codeValueToDensity / nGamma))
+
+            return (codeLinear - blackLinear)/(1.0 - blackLinear)
+
+        cs.toReferenceTransforms = []
+
+        if transferFunction == 'REDlogFilm':
+            data = array.array('f', "\0" * lutResolution1d * 4)
+            for c in range(lutResolution1d):
+                data[c] = cineonToLinear(1023.0*c/(lutResolution1d-1))
+
+            lut = "CineonLog_to_linear.spi1d"
+            WriteSPI1D(lutDir + "/" + lut, 0.0, 1.0, data, lutResolution1d, 1)
+
+            cs.toReferenceTransforms.append( {
+                'type':'lutFile', 
+                'path':lut, 
+                'interpolation':'linear', 
+                'direction':'forward'
+            } )
+
+        if gamut == 'DRAGONcolor':
+            cs.toReferenceTransforms.append( {
+                'type':'matrix',
+                'matrix':mat44FromMat33([0.532279, 0.376648, 0.091073, 
+                                        0.046344, 0.974513, -0.020860, 
+                                        -0.053976, -0.000320, 1.054267]),
+                'direction':'forward'
+            })
+        elif gamut == 'REDcolor3':
+            cs.toReferenceTransforms.append( {
+                'type':'matrix',
+                'matrix':mat44FromMat33([0.512136, 0.360370, 0.127494, 
+                                        0.070377, 0.903884, 0.025737, 
+                                        -0.020824, 0.017671, 1.003123]),
+                'direction':'forward'
+            })
+        elif gamut == 'REDcolor2':
+            cs.toReferenceTransforms.append( {
+                'type':'matrix',
+                'matrix':mat44FromMat33([0.480997, 0.402289, 0.116714, 
+                                        -0.004938, 1.000154, 0.004781, 
+                                        -0.105257, 0.025320, 1.079907]),
+                'direction':'forward'
+            })
+
+        cs.fromReferenceTransforms = []
+        return cs
+
+    # Full conversion
+    REDlogFilmDRAGON = createREDlogFilm("DRAGONcolor", "REDlogFilm", name="REDlogFilm")
+    configData['colorSpaces'].append(REDlogFilmDRAGON)
+
+    REDlogFilmREDcolor2 = createREDlogFilm("REDcolor2", "REDlogFilm", name="REDlogFilm")
+    configData['colorSpaces'].append(REDlogFilmREDcolor2)
+
+    REDlogFilmREDcolor3 = createREDlogFilm("REDcolor3", "REDlogFilm", name="REDlogFilm")
+    configData['colorSpaces'].append(REDlogFilmREDcolor3)
+
+    # Linearization only
+    REDlogFilmDRAGON = createREDlogFilm("", "REDlogFilm", name="REDlogFilm")
+    configData['colorSpaces'].append(REDlogFilmDRAGON)
+
+    # Primaries only
+    REDlogFilmDRAGON = createREDlogFilm("DRAGONcolor", "", name="REDlogFilm")
+    configData['colorSpaces'].append(REDlogFilmDRAGON)
+
+    REDlogFilmREDcolor2 = createREDlogFilm("REDcolor2", "", name="REDlogFilm")
+    configData['colorSpaces'].append(REDlogFilmREDcolor2)
+
+    REDlogFilmREDcolor3 = createREDlogFilm("REDcolor3", "", name="REDlogFilm")
+    configData['colorSpaces'].append(REDlogFilmREDcolor3)
+
+    #
+    # Canon-Log to ACES
+    #
+    def createCanonLog(gamut, transferFunction, name='Canon-Log'):
+        name = "%s - %s" % (transferFunction, gamut)
+        if transferFunction == "":
+            name = "Linear - %s" % gamut
+        if gamut == "":
+            name = "%s" % transferFunction
+
+        cs = ColorSpace(name)
+        cs.description = name
+        cs.equalityGroup = ''
+        cs.family = 'Canon'
+        cs.isData=False
+
+        def legalToFull(codeValue):
+            return (codeValue - 64.0)/(940.0 - 64.0)
+
+        def canonLogToLinear(codeValue):
+            # log = fullToLegal(c1 * log10(c2*linear + 1) + c3)
+            # linear = (pow(10, (legalToFul(log) - c3)/c1) - 1)/c2
+            c1 = 0.529136
+            c2 = 10.1596
+            c3 = 0.0730597
+
+            linear = (pow(10.0, (legalToFull(codeValue) - c3)/c1) -1.0)/c2
+            linear = 0.9 * linear
+            #print( codeValue, linear )
+            return linear
+
+        cs.toReferenceTransforms = []
+
+        if transferFunction == "Canon-Log":
+            data = array.array('f', "\0" * lutResolution1d * 4)
+            for c in range(lutResolution1d):
+                data[c] = canonLogToLinear(1023.0*c/(lutResolution1d-1))
+
+            lut = "%s_to_linear.spi1d" % transferFunction
+            WriteSPI1D(lutDir + "/" + lut, 0.0, 1.0, data, lutResolution1d, 1)
+
+            cs.toReferenceTransforms.append( {
+                'type':'lutFile', 
+                'path':lut, 
+                'interpolation':'linear', 
+                'direction':'forward'
+            } )
+
+        if gamut == 'Rec. 709 Daylight':
+            cs.toReferenceTransforms.append( {
+                'type':'matrix',
+                'matrix':[0.561538969, 0.402060105, 0.036400926, 0.0, 
+                            0.092739623, 0.924121198, -0.016860821, 0.0, 
+                            0.084812961, 0.006373835, 0.908813204, 0.0, 
+                            0,0,0,1.0],
+                'direction':'forward'
+            })
+        elif gamut == 'Rec. 709 Tungsten':
+            cs.toReferenceTransforms.append( {
+                'type':'matrix',
+                'matrix':[0.566996399, 0.365079418, 0.067924183, 0.0, 
+                            0.070901044, 0.880331008, 0.048767948, 0.0, 
+                            0.073013542, -0.066540862, 0.99352732, 0.0, 
+                            0,0,0,1.0],
+                'direction':'forward'
+            })
+        elif gamut == 'DCI-P3 Daylight':
+            cs.toReferenceTransforms.append( {
+                'type':'matrix',
+                'matrix':[0.607160575, 0.299507286, 0.093332140, 0.0, 
+                            0.004968120, 1.050982224, -0.055950343, 0.0, 
+                            -0.007839939, 0.000809127, 1.007030813, 0.0, 
+                            0,0,0,1.0],
+                'direction':'forward'
+            })
+        elif gamut == 'DCI-P3 Tungsten':
+            cs.toReferenceTransforms.append( {
+                'type':'matrix',
+                'matrix':[0.650279125, 0.253880169, 0.095840706, 0.0, 
+                            -0.026137986, 1.017900530, 0.008237456, 0.0, 
+                            0.007757558, -0.063081669, 1.055324110, 0.0, 
+                            0,0,0,1.0],
+                'direction':'forward'
+            })
+        elif gamut == 'Cinema Gamut Daylight':
+            cs.toReferenceTransforms.append( {
+                'type':'matrix',
+                'matrix':[0.763064455, 0.149021161, 0.087914384, 0.0, 
+                            0.003657457, 1.10696038, -0.110617837, 0.0, 
+                            -0.009407794,-0.218383305, 1.227791099, 0.0, 
+                            0,0,0,1.0],
+                'direction':'forward'
+            })
+        elif gamut == 'Cinema Gamut Tungsten':
+            cs.toReferenceTransforms.append( {
+                'type':'matrix',
+                'matrix':[0.817416293, 0.090755698, 0.091828009, 0.0, 
+                            -0.035361374, 1.065690585, -0.030329211, 0.0, 
+                            0.010390366, -0.299271107, 1.288880741, 0.0, 
+                            0,0,0,1.0],
+                'direction':'forward'
+            })
+
+        cs.fromReferenceTransforms = []
+        return cs
+
+    # Full conversion
+    CanonLog1 = createCanonLog("Rec. 709 Daylight", "Canon-Log", name="Canon-Log")
+    configData['colorSpaces'].append(CanonLog1)
+
+    CanonLog2 = createCanonLog("Rec. 709 Tungsten", "Canon-Log", name="Canon-Log")
+    configData['colorSpaces'].append(CanonLog2)
+
+    CanonLog3 = createCanonLog("DCI-P3 Daylight", "Canon-Log", name="Canon-Log")
+    configData['colorSpaces'].append(CanonLog3)
+
+    CanonLog4 = createCanonLog("DCI-P3 Tungsten", "Canon-Log", name="Canon-Log")
+    configData['colorSpaces'].append(CanonLog4)
+
+    CanonLog5 = createCanonLog("Cinema Gamut Daylight", "Canon-Log", name="Canon-Log")
+    configData['colorSpaces'].append(CanonLog5)
+
+    CanonLog6 = createCanonLog("Cinema Gamut Tungsten", "Canon-Log", name="Canon-Log")
+    configData['colorSpaces'].append(CanonLog6)
+
+    # Linearization only
+    CanonLog7 = createCanonLog('', "Canon-Log", name="Canon-Log")
+    configData['colorSpaces'].append(CanonLog7)
+
+    # Primaries only
+    CanonLog8 = createCanonLog("Rec. 709 Daylight", "", name="Canon-Log")
+    configData['colorSpaces'].append(CanonLog8)
+
+    CanonLog9 = createCanonLog("Rec. 709 Tungsten", "", name="Canon-Log")
+    configData['colorSpaces'].append(CanonLog9)
+
+    CanonLog10 = createCanonLog("DCI-P3 Daylight", "", name="Canon-Log")
+    configData['colorSpaces'].append(CanonLog10)
+
+    CanonLog11 = createCanonLog("DCI-P3 Tungsten", "", name="Canon-Log")
+    configData['colorSpaces'].append(CanonLog11)
+
+    CanonLog12 = createCanonLog("Cinema Gamut Daylight", "", name="Canon-Log")
+    configData['colorSpaces'].append(CanonLog12)
+
+    CanonLog13 = createCanonLog("Cinema Gamut Tungsten", "", name="Canon-Log")
+    configData['colorSpaces'].append(CanonLog13)
+
+    #
+    # SLog to ACES
+    #
+    def createSlog(gamut, transferFunction, name='S-Log3'):
+        name = "%s - %s" % (transferFunction, gamut)
+        if transferFunction == "":
+            name = "Linear - %s" % gamut
+        if gamut == "":
+            name = "%s" % transferFunction
+
+        cs = ColorSpace(name)
+        cs.description = name
+        cs.equalityGroup = ''
+        cs.family = 'Sony'
+        cs.isData=False
+
+        def sLog1ToLinear(SLog):
+            b = 64.
+            ab = 90.
+            w = 940.
+
+            if (SLog >= ab):
+                lin = ( pow(10., ( ( ( SLog - b) / ( w - b) - 0.616596 - 0.03) / 0.432699)) - 0.037584) * 0.9
+            else:
+                lin = ( ( ( SLog - b) / ( w - b) - 0.030001222851889303) / 5.) * 0.9 
+            return lin
+
+        def sLog2ToLinear(SLog):
+            b = 64.
+            ab = 90.
+            w = 940.
+
+            if (SLog >= ab):
+                lin = ( 219. * ( pow(10., ( ( ( SLog - b) / ( w - b) - 0.616596 - 0.03) / 0.432699)) - 0.037584) / 155.) * 0.9
+            else:
+                lin = ( ( ( SLog - b) / ( w - b) - 0.030001222851889303) / 3.53881278538813) * 0.9
+            return lin
+
+        def sLog3ToLinear(codeValue):
+            if codeValue >= (171.2102946929):
+                linear = pow(10.0, ((codeValue - 420.0) / 261.5)) * (0.18 + 0.01) - 0.01
+            else:
+                linear = (codeValue - 95.0)*0.01125000/(171.2102946929 - 95.0)
+            #print( codeValue, linear )
+            return linear
+
+        cs.toReferenceTransforms = []
+
+        if transferFunction == "S-Log1":
+            data = array.array('f', "\0" * lutResolution1d * 4)
+            for c in range(lutResolution1d):
+                data[c] = sLog1ToLinear(1023.0*c/(lutResolution1d-1))
+
+            lut = "%s_to_linear.spi1d" % transferFunction
+            WriteSPI1D(lutDir + "/" + lut, 0.0, 1.0, data, lutResolution1d, 1)
+
+            #print( "Writing %s" % lut)
+
+            cs.toReferenceTransforms.append( {
+                'type':'lutFile', 
+                'path':lut, 
+                'interpolation':'linear', 
+                'direction':'forward'
+            } )
+        elif transferFunction == "S-Log2":
+            data = array.array('f', "\0" * lutResolution1d * 4)
+            for c in range(lutResolution1d):
+                data[c] = sLog2ToLinear(1023.0*c/(lutResolution1d-1))
+
+            lut = "%s_to_linear.spi1d" % transferFunction
+            WriteSPI1D(lutDir + "/" + lut, 0.0, 1.0, data, lutResolution1d, 1)
+
+            #print( "Writing %s" % lut)
+
+            cs.toReferenceTransforms.append( {
+                'type':'lutFile', 
+                'path':lut, 
+                'interpolation':'linear', 
+                'direction':'forward'
+            } )
+        elif transferFunction == "S-Log3":
+            data = array.array('f', "\0" * lutResolution1d * 4)
+            for c in range(lutResolution1d):
+                data[c] = sLog3ToLinear(1023.0*c/(lutResolution1d-1))
+
+            lut = "%s_to_linear.spi1d" % transferFunction
+            WriteSPI1D(lutDir + "/" + lut, 0.0, 1.0, data, lutResolution1d, 1)
+
+            #print( "Writing %s" % lut)
+
+            cs.toReferenceTransforms.append( {
+                'type':'lutFile', 
+                'path':lut, 
+                'interpolation':'linear', 
+                'direction':'forward'
+            } )
+
+        if gamut == 'S-Gamut':
+            cs.toReferenceTransforms.append( {
+                'type':'matrix',
+                'matrix':mat44FromMat33([0.754338638, 0.133697046, 0.111968437,
+                                        0.021198141, 1.005410934, -0.026610548, 
+                                        -0.009756991, 0.004508563, 1.005253201]),
+                'direction':'forward'
+            })
+        elif gamut == 'S-Gamut Daylight':
+            cs.toReferenceTransforms.append( {
+                'type':'matrix',
+                'matrix':mat44FromMat33([0.8764457030, 0.0145411681, 0.1090131290,
+                                        0.0774075345, 0.9529571767, -0.0303647111, 
+                                        0.0573564351, -0.1151066335, 1.0577501984]),
+                'direction':'forward'
+            })
+        elif gamut == 'S-Gamut Tungsten':
+            cs.toReferenceTransforms.append( {
+                'type':'matrix',
+                'matrix':mat44FromMat33([1.0110238740, -0.1362526051, 0.1252287310, 
+                            0.1011994504, 0.9562196265, -0.0574190769,
+                            0.0600766530, -0.1010185315, 1.0409418785]),
+                'direction':'forward'
+            })
+        elif gamut == 'S-Gamut3.Cine':
+            cs.toReferenceTransforms.append( {
+                'type':'matrix',
+                'matrix':mat44FromMat33([0.6387886672, 0.2723514337, 0.0888598992, 
+                                        -0.0039159061, 1.0880732308, -0.0841573249, 
+                                        -0.0299072021, -0.0264325799, 1.0563397820]),
+                'direction':'forward'
+            })
+        elif gamut == 'S-Gamut3':
+            cs.toReferenceTransforms.append( {
+                'type':'matrix',
+                'matrix':mat44FromMat33([0.7529825954, 0.1433702162, 0.1036471884, 
+                            0.0217076974, 1.0153188355, -0.0370265329, 
+                            -0.0094160528, 0.0033704179, 1.0060456349]),
+                'direction':'forward'
+            })
+
+        cs.fromReferenceTransforms = []
+        return cs
+
+    # SLog1
+    SLog1SGamut = createSlog("S-Gamut", "S-Log1", name="S-Log")
+    configData['colorSpaces'].append(SLog1SGamut)
+
+    # SLog2
+    SLog2SGamut = createSlog("S-Gamut", "S-Log2", name="S-Log2")
+    configData['colorSpaces'].append(SLog2SGamut)
+
+    SLog2SGamutDaylight = createSlog("S-Gamut Daylight", "S-Log2", name="S-Log2")
+    configData['colorSpaces'].append(SLog2SGamutDaylight)
+
+    SLog2SGamutTungsten = createSlog("S-Gamut Tungsten", "S-Log2", name="S-Log2")
+    configData['colorSpaces'].append(SLog2SGamutTungsten)
+
+    # SLog3
+    SLog3SGamut3Cine = createSlog("S-Gamut3.Cine", "S-Log3", name="S-Log3")
+    configData['colorSpaces'].append(SLog3SGamut3Cine)
+
+    SLog3SGamut3 = createSlog("S-Gamut3", "S-Log3", name="S-Log3")
+    configData['colorSpaces'].append(SLog3SGamut3)
+
+    # Linearization only
+    SLog1 = createSlog("", "S-Log1", name="S-Log")
+    configData['colorSpaces'].append(SLog1)
+
+    SLog2 = createSlog("", "S-Log2", name="S-Log2")
+    configData['colorSpaces'].append(SLog2)
+
+    SLog3 = createSlog("", "S-Log3", name="S-Log3")
+    configData['colorSpaces'].append(SLog3)
+
+    # Primaries only
+    SGamut = createSlog("S-Gamut", "", name="S-Log")
+    configData['colorSpaces'].append(SGamut)
+
+    SGamutDaylight = createSlog("S-Gamut Daylight", "", name="S-Log2")
+    configData['colorSpaces'].append(SGamutDaylight)
+
+    SGamutTungsten = createSlog("S-Gamut Tungsten", "", name="S-Log2")
+    configData['colorSpaces'].append(SGamutTungsten)
+
+    SGamut3Cine = createSlog("S-Gamut3.Cine", "", name="S-Log3")
+    configData['colorSpaces'].append(SGamut3Cine)
+
+    SGamut3 = createSlog("S-Gamut3", "", name="S-Log3")
+    configData['colorSpaces'].append(SGamut3)
+
+    #
+    # LogC to ACES
+    #
+    def createLogC(gamut, transferFunction, exposureIndex, name='LogC'):
+        name = "%s (EI%s) - %s" % (transferFunction, exposureIndex, gamut)
+        if transferFunction == "":
+            name = "Linear - %s" % gamut
+        if gamut == "":
+            name = "%s (EI%s)" % (transferFunction, exposureIndex)
+
+        cs = ColorSpace(name)
+        cs.description = name
+        cs.equalityGroup = ''
+        cs.family = 'ARRI'
+        cs.isData=False
+
+        # Globals
+        IDT_maker_version = "0.08"
+
+        nominalEI = 400.0
+        blackSignal = 0.003907
+        midGraySignal = 0.01
+        encodingGain = 0.256598
+        encodingOffset = 0.391007
+
+        def gainForEI(EI) :
+            return (math.log(EI/nominalEI)/math.log(2) * (0.89 - 1) / 3 + 1) * encodingGain
+
+        def LogCInverseParametersForEI(EI) :
+            cut = 1.0 / 9.0
+            slope = 1.0 / (cut * math.log(10))
+            offset = math.log10(cut) - slope * cut
+            gain = EI / nominalEI
+            gray = midGraySignal / gain
+            # The higher the EI, the lower the gamma
+            encGain = gainForEI(EI)
+            encOffset = encodingOffset
+            for i in range(0,3) :
+                nz = ((95.0 / 1023.0 - encOffset) / encGain - offset) / slope
+                encOffset = encodingOffset - math.log10(1 + nz) * encGain
+            # Calculate some intermediate values
+            a = 1.0 / gray
+            b = nz - blackSignal / gray
+            e = slope * a * encGain
+            f = encGain * (slope * b + offset) + encOffset
+            # Manipulations so we can return relative exposure
+            s = 4 / (0.18 * EI)
+            t = blackSignal
+            b = b + a * t
+            a = a * s
+            f = f + e * t
+            e = e * s
+            return { 'a' : a,
+                     'b' : b,
+                     'cut' : (cut - b) / a,
+                     'c' : encGain,
+                     'd' : encOffset,
+                     'e' : e,
+                     'f' : f }
+
+        def logCtoLinear(codeValue, exposureIndex):
+            p = LogCInverseParametersForEI(exposureIndex)
+            breakpoint = p['e'] * p['cut'] + p['f']
+            if (codeValue > breakpoint):
+                linear = (pow(10,(codeValue/1023.0 - p['d']) / p['c']) - p['b']) / p['a']
+            else:
+                linear = (codeValue/1023.0 - p['f']) / p['e']
+
+            #print( codeValue, linear )
+            return linear
+
+
+        cs.toReferenceTransforms = []
+
+        if transferFunction == "V3 LogC":
+            data = array.array('f', "\0" * lutResolution1d * 4)
+            for c in range(lutResolution1d):
+                data[c] = logCtoLinear(1023.0*c/(lutResolution1d-1), int(exposureIndex))
+
+            lut = "%s_to_linear.spi1d" % ("%s_%s" % (transferFunction, exposureIndex))
+            WriteSPI1D(lutDir + "/" + lut, 0.0, 1.0, data, lutResolution1d, 1)
+
+            #print( "Writing %s" % lut)
+            cs.toReferenceTransforms.append( {
+                'type':'lutFile', 
+                'path':lut, 
+                'interpolation':'linear', 
+                'direction':'forward'
+            } )
+
+        if gamut == 'Wide Gamut':
+            cs.toReferenceTransforms.append( {
+                'type':'matrix',
+                'matrix':mat44FromMat33([0.680206, 0.236137, 0.083658, 
+                            0.085415, 1.017471, -0.102886, 
+                            0.002057, -0.062563, 1.060506]),
+                'direction':'forward'
+            })
+
+        cs.fromReferenceTransforms = []
+        return cs
+
+    transferFunction = "V3 LogC"
+    gamut = "Wide Gamut"
+    #EIs = [160.0, 200.0, 250.0, 320.0, 400.0, 500.0, 640.0, 800.0, 1000.0, 1280.0, 1600.0, 2000.0, 2560.0, 3200.0]
+    EIs = [160, 200, 250, 320, 400, 500, 640, 800, 1000, 1280, 1600, 2000, 2560, 3200]
+    defaultEI = 800
+
+    # Full conversion
+    for EI in EIs:
+        LogCEIfull = createLogC(gamut, transferFunction, EI, name="LogC")
+        configData['colorSpaces'].append(LogCEIfull)
+
+    # Linearization only
+    for EI in [800]:
+        LogCEIlinearization = createLogC("", transferFunction, EI, name="LogC")
+        configData['colorSpaces'].append(LogCEIlinearization)
+
+    # Primaries
+    LogCEIprimaries = createLogC(gamut, "", defaultEI, name="LogC")
+    configData['colorSpaces'].append(LogCEIprimaries)
+
+    #
+    # Generic log transform
+    #
+    def createGenericLog(name='log', 
+        minValue=0.0, 
+        maxValue=1.0, 
+        inputScale=1.0,
+        middleGrey=0.18,
+        minExposure=-6.0,
+        maxExposure=6.5,
+        lutResolution1d=lutResolution1d):
+        cs = ColorSpace(name)
+        cs.description = "The %s color space" % name
+        cs.equalityGroup = name
+        cs.family = 'Utility'
+        cs.isData=False
+
+        ctls = [
+            #'%s/logShaper/logShaper16i_to_aces_param.ctl' % acesCTLReleaseDir
+            '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl' % acesCTLReleaseDir
+        ]
+        lut = "%s_to_aces.spi1d" % name
+
+        generate1dLUTFromCTL( lutDir + "/" + lut, 
+            ctls, 
+            lutResolution1d, 
+            'float', 
+            inputScale,
+            1.0, 
+            {
+                'middleGrey'  : middleGrey,
+                'minExposure' : minExposure,
+                'maxExposure' : maxExposure
+            },
+            cleanup, 
+            acesCTLReleaseDir,
+            minValue,
+            maxValue)
+
+        cs.toReferenceTransforms = []
+        cs.toReferenceTransforms.append( {
+            'type':'lutFile', 
+            'path':lut, 
+            'interpolation':'linear', 
+            'direction':'forward'
+        } )
+
+        cs.fromReferenceTransforms = []
+        return cs
+
+    #
+    # ACES LMTs
+    #
+    def createACESLMT(lmtName, 
+        lmtValues,
+        shaperInfo,
+        lutResolution1d=1024, 
+        lutResolution3d=64, 
+        cleanup=True):
+        cs = ColorSpace("%s" % lmtName)
+        cs.description = "The ACES Look Transform: %s" % lmtName
+        cs.equalityGroup = ''
+        cs.family = 'Look'
+        cs.isData=False
+
+        import pprint
+        pprint.pprint( lmtValues )
+
+        #
+        # Generate the shaper transform
+        #
+        (shaperName, shaperToACESCTL, shaperFromACESCTL, shaperInputScale, shaperParams) = shaperInfo
+
+        shaperLut = "%s_to_aces.spi1d" % shaperName
+        if( not os.path.exists( lutDir + "/" + shaperLut ) ):
+            ctls = [
+                shaperToACESCTL % acesCTLReleaseDir
+            ]
+            generate1dLUTFromCTL( lutDir + "/" + shaperLut, 
+                ctls, 
+                lutResolution1d, 
+                'float', 
+                1.0/shaperInputScale,
+                1.0, 
+                shaperParams,
+                cleanup, 
+                acesCTLReleaseDir)
+
+        shaperOCIOTransform = {
+            'type':'lutFile', 
+            'path':shaperLut, 
+            'interpolation':'linear', 
+            'direction':'inverse'
+        }
+
+        #
+        # Generate the forward transform
+        #
+        cs.fromReferenceTransforms = []
+
+        if 'transformCTL' in lmtValues:
+            ctls = [
+                shaperToACESCTL % acesCTLReleaseDir, 
+                '%s/%s' % (acesCTLReleaseDir, lmtValues['transformCTL'])
+            ]
+            lut = "%s.%s.spi3d" % (shaperName, lmtName)
+
+            generate3dLUTFromCTL( lutDir + "/" + lut, 
+                ctls, 
+                lutResolution3d, 
+                'float', 
+                1.0/shaperInputScale,
+                1.0, 
+                shaperParams,
+                cleanup, 
+                acesCTLReleaseDir )
+
+            cs.fromReferenceTransforms.append( shaperOCIOTransform )
+            cs.fromReferenceTransforms.append( {
+                'type':'lutFile', 
+                'path':lut, 
+                'interpolation':'tetrahedral', 
+                'direction':'forward'
+            } )
+
+        #
+        # Generate the inverse transform
+        #
+        cs.toReferenceTransforms = []
+
+        if 'transformCTLInverse' in lmtValues:
+            ctls = [
+                '%s/%s' % (acesCTLReleaseDir, odtValues['transformCTLInverse']),
+                shaperFromACESCTL % acesCTLReleaseDir
+            ]
+            lut = "Inverse.%s.%s.spi3d" % (odtName, shaperName)
+
+            generate3dLUTFromCTL( lutDir + "/" + lut, 
+                ctls, 
+                lutResolution3d, 
+                'half', 
+                1.0,
+                shaperInputScale, 
+                shaperParams,
+                cleanup, 
+                acesCTLReleaseDir )
+
+            cs.toReferenceTransforms.append( {
+                'type':'lutFile', 
+                'path':lut, 
+                'interpolation':'tetrahedral', 
+                'direction':'forward'
+            } )
+
+            shaperInverse = shaperOCIOTransform.copy()
+            shaperInverse['direction'] = 'forward'
+            cs.toReferenceTransforms.append( shaperInverse )
+
+        return cs
+
+    #
+    # LMT Shaper
+    #
+
+    lmtLutResolution1d = max(4096, lutResolution1d)
+    lmtLutResolution3d = max(65, lutResolution3d)
+
+    # Log 2 shaper
+    lmtShaperName = 'lmtShaper'
+    lmtParams = {
+        'middleGrey'  : 0.18,
+        'minExposure' : -10.0,
+        'maxExposure' : 6.5
+    }
+    lmtShaper = createGenericLog(name=lmtShaperName, 
+        middleGrey=lmtParams['middleGrey'], 
+        minExposure=lmtParams['minExposure'], 
+        maxExposure=lmtParams['maxExposure'],
+        lutResolution1d=lmtLutResolution1d)
+    configData['colorSpaces'].append(lmtShaper)
+
+    shaperInputScale_genericLog2 = 1.0
+
+    # Log 2 shaper name and CTL transforms bundled up
+    lmtShaperData = [
+        lmtShaperName, 
+        '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl',
+        '%s/utilities/ACESlib.OCIO_shaper_lin_to_log2_param.a1.0.0.ctl',
+        #'%s/logShaper/logShaper16i_to_aces_param.ctl',
+        #'%s/logShaper/aces_to_logShaper16i_param.ctl',
+        shaperInputScale_genericLog2,
+        lmtParams
+    ]
+
+    sortedLMTs = sorted(lmtInfo.iteritems(), key=lambda x: x[1])
+    print( sortedLMTs )
+    for lmt in sortedLMTs:
+        (lmtName, lmtValues) = lmt
+        cs = createACESLMT(
+            lmtValues['transformUserName'], 
+            lmtValues,
+            lmtShaperData,
+            lmtLutResolution1d,
+            lmtLutResolution3d,
+            cleanup)
+        configData['colorSpaces'].append(cs)
+
+    #
+    # ACES RRT with the supplied ODT
+    #
+    def createACESRRTplusODT(odtName, 
+        odtValues,
+        shaperInfo,
+        lutResolution1d=1024, 
+        lutResolution3d=64, 
+        cleanup=True):
+        cs = ColorSpace("%s" % odtName)
+        cs.description = "%s - %s Output Transform" % (odtValues['transformUserNamePrefix'], odtName)
+        cs.equalityGroup = ''
+        cs.family = 'Output'
+        cs.isData=False
+
+        import pprint
+        pprint.pprint( odtValues )
+
+        #
+        # Generate the shaper transform
+        #
+        #if 'shaperCTL' in odtValues:
+        (shaperName, shaperToACESCTL, shaperFromACESCTL, shaperInputScale, shaperParams) = shaperInfo
+
+        if 'legalRange' in odtValues:
+            shaperParams['legalRange'] = odtValues['legalRange']
+        else:
+            shaperParams['legalRange'] = 0
+
+        shaperLut = "%s_to_aces.spi1d" % shaperName
+        if( not os.path.exists( lutDir + "/" + shaperLut ) ):
+            ctls = [
+                shaperToACESCTL % acesCTLReleaseDir
+            ]
+            generate1dLUTFromCTL( lutDir + "/" + shaperLut, 
+                ctls, 
+                lutResolution1d, 
+                'float', 
+                1.0/shaperInputScale,
+                1.0, 
+                shaperParams,
+                cleanup, 
+                acesCTLReleaseDir)
+
+        shaperOCIOTransform = {
+            'type':'lutFile', 
+            'path':shaperLut, 
+            'interpolation':'linear', 
+            'direction':'inverse'
+        }
+
+        #
+        # Generate the forward transform
+        #
+        cs.fromReferenceTransforms = []
+
+        if 'transformLUT' in odtValues:
+            # Copy into the lut dir
+            transformLUTFileName = os.path.basename(odtValues['transformLUT'])
+            lut = lutDir + "/" + transformLUTFileName
+            shutil.copy(odtValues['transformLUT'], lut)
+
+            cs.fromReferenceTransforms.append( shaperOCIOTransform )
+            cs.fromReferenceTransforms.append( {
+                'type':'lutFile', 
+                'path': transformLUTFileName, 
+                'interpolation':'tetrahedral', 
+                'direction':'forward'
+            } )
+        elif 'transformCTL' in odtValues:
+            #shaperLut
+
+            ctls = [
+                shaperToACESCTL % acesCTLReleaseDir, 
+                '%s/rrt/RRT.a1.0.0.ctl' % acesCTLReleaseDir, 
+                '%s/odt/%s' % (acesCTLReleaseDir, odtValues['transformCTL'])
+            ]
+            lut = "%s.RRT.a1.0.0.%s.spi3d" % (shaperName, odtName)
+
+            generate3dLUTFromCTL( lutDir + "/" + lut, 
+                #shaperLUT,
+                ctls, 
+                lutResolution3d, 
+                'float', 
+                1.0/shaperInputScale,
+                1.0, 
+                shaperParams,
+                cleanup, 
+                acesCTLReleaseDir )
+
+            cs.fromReferenceTransforms.append( shaperOCIOTransform )
+            cs.fromReferenceTransforms.append( {
+                'type':'lutFile', 
+                'path':lut, 
+                'interpolation':'tetrahedral', 
+                'direction':'forward'
+            } )
+
+        #
+        # Generate the inverse transform
+        #
+        cs.toReferenceTransforms = []
+
+        if 'transformLUTInverse' in odtValues:
+            # Copy into the lut dir
+            transformLUTInverseFileName = os.path.basename(odtValues['transformLUTInverse'])
+            lut = lutDir + "/" + transformLUTInverseFileName
+            shutil.copy(odtValues['transformLUTInverse'], lut)
+
+            cs.toReferenceTransforms.append( {
+                'type':'lutFile', 
+                'path': transformLUTInverseFileName, 
+                'interpolation':'tetrahedral', 
+                'direction':'forward'
+            } )
+
+            shaperInverse = shaperOCIOTransform.copy()
+            shaperInverse['direction'] = 'forward'
+            cs.toReferenceTransforms.append( shaperInverse )
+        elif 'transformCTLInverse' in odtValues:
+            ctls = [
+                '%s/odt/%s' % (acesCTLReleaseDir, odtValues['transformCTLInverse']),
+                '%s/rrt/InvRRT.a1.0.0.ctl' % acesCTLReleaseDir,
+                shaperFromACESCTL % acesCTLReleaseDir
+            ]
+            lut = "InvRRT.a1.0.0.%s.%s.spi3d" % (odtName, shaperName)
+
+            generate3dLUTFromCTL( lutDir + "/" + lut, 
+                #None,
+                ctls, 
+                lutResolution3d, 
+                'half', 
+                1.0,
+                shaperInputScale, 
+                shaperParams,
+                cleanup, 
+                acesCTLReleaseDir )
+
+            cs.toReferenceTransforms.append( {
+                'type':'lutFile', 
+                'path':lut, 
+                'interpolation':'tetrahedral', 
+                'direction':'forward'
+            } )
+
+            shaperInverse = shaperOCIOTransform.copy()
+            shaperInverse['direction'] = 'forward'
+            cs.toReferenceTransforms.append( shaperInverse )
+
+        return cs
+
+    #
+    # RRT/ODT shaper options
+    #
+    shaperData = {}
+
+    # Log 2 shaper
+    log2ShaperName = shaperName
+    log2Params = {
+        'middleGrey'  : 0.18,
+        'minExposure' : -6.0,
+        'maxExposure' : 6.5
+    }
+    log2Shaper = createGenericLog(name=log2ShaperName, 
+        middleGrey=log2Params['middleGrey'], 
+        minExposure=log2Params['minExposure'], 
+        maxExposure=log2Params['maxExposure'])
+    configData['colorSpaces'].append(log2Shaper)
+
+    shaperInputScale_genericLog2 = 1.0
+
+    # Log 2 shaper name and CTL transforms bundled up
+    log2ShaperData = [
+        log2ShaperName, 
+        '%s/utilities/ACESlib.OCIO_shaper_log2_to_lin_param.a1.0.0.ctl',
+        '%s/utilities/ACESlib.OCIO_shaper_lin_to_log2_param.a1.0.0.ctl',
+        #'%s/logShaper/logShaper16i_to_aces_param.ctl',
+        #'%s/logShaper/aces_to_logShaper16i_param.ctl',
+        shaperInputScale_genericLog2,
+        log2Params
+    ]
+
+    shaperData[log2ShaperName] = log2ShaperData
+
+    #
+    # Shaper that also includes the AP1 primaries
+    # - Needed for some LUT baking steps
+    #
+    log2ShaperAP1 = createGenericLog(name=log2ShaperName, 
+        middleGrey=log2Params['middleGrey'], 
+        minExposure=log2Params['minExposure'], 
+        maxExposure=log2Params['maxExposure'])
+    log2ShaperAP1.name = "%s AP1" % log2ShaperAP1.name
+    # AP1 primaries to AP0 primaries
+    log2ShaperAP1.toReferenceTransforms.append( {
+        'type':'matrix',
+        'matrix':mat44FromMat33(acesAP1toAP0),
+        'direction':'forward'
+    })
+    configData['colorSpaces'].append(log2ShaperAP1)
+
+    #
+    # Choose your shaper
+    #
+    # XXX
+    # Shaper name. Should really be automated or made a user choice
+    #
+    # Options: aceslogShaper, aceslogScaledShaper, log2Shaper
+    #shaperName = 'log2Shaper'
+
+    #if shaperName in shaperData:
+    #    rrtShaperName = shaperName
+    #    rrtShaper = shaperData[shaperName]
+    #else:
+
+    rrtShaperName = log2ShaperName
+    rrtShaper = log2ShaperData
+
+    #
+    # RRT + ODT Combinations
+    #
+    #for odtName, odtValues in odtInfo.iteritems():
+    sortedOdts = sorted(odtInfo.iteritems(), key=lambda x: x[1])
+    print( sortedOdts )
+    for odt in sortedOdts:
+        (odtName, odtValues) = odt
+
+        # Have to handle ODTs that can generate either legal or full output
+        if odtName in ['Academy.Rec2020_100nits_dim.a1.0.0', 
+                'Academy.Rec709_100nits_dim.a1.0.0',
+                'Academy.Rec709_D60sim_100nits_dim.a1.0.0']:
+            odtNameLegal = '%s - Legal' % odtValues['transformUserName']
+        else:
+            odtNameLegal = odtValues['transformUserName']
+
+        odtLegal = odtValues.copy()
+        odtLegal['legalRange'] = 1
+
+        cs = createACESRRTplusODT(
+            odtNameLegal, 
+            odtLegal,
+            rrtShaper,
+            lutResolution1d,
+            lutResolution3d,
+            cleanup)
+        configData['colorSpaces'].append(cs)
+
+        # Create a display entry using this color space
+        configData['displays'][odtNameLegal] = { 
+            'Linear':ACES, 
+            'Log':ACEScc, 
+            'Output Transform':cs }
+
+        if odtName in ['Academy.Rec2020_100nits_dim.a1.0.0', 
+                'Academy.Rec709_100nits_dim.a1.0.0', 
+                'Academy.Rec709_D60sim_100nits_dim.a1.0.0']:
+
+            print( "Generating full range ODT for %s" % odtName)
+
+            odtNameFull = "%s - Full" % odtValues['transformUserName']
+            odtFull = odtValues.copy()
+            odtFull['legalRange'] = 0
+
+            csFull = createACESRRTplusODT(
+                odtNameFull, 
+                odtFull,
+                rrtShaper,
+                lutResolution1d,
+                lutResolution3d,
+                cleanup)
+            configData['colorSpaces'].append(csFull)
+
+            # Create a display entry using this color space
+            configData['displays'][odtNameFull] = { 
+                'Linear':ACES, 
+                'Log':ACEScc, 
+                'Output Transform':csFull }
+
+
+    print( "generateLUTs - end" )
+    return configData
+
+def generateBakedLUTs(odtInfo, shaperName, bakedDir, configPath, lutResolution1d, lutResolution3d, lutResolutionShaper=1024):
+
+    # Add the legal and full variations into this list
+    odtInfoC = dict(odtInfo)
+    for odtCTLName, odtValues in odtInfo.iteritems():
+        if odtCTLName in ['Academy.Rec2020_100nits_dim.a1.0.0', 
+            'Academy.Rec709_100nits_dim.a1.0.0',
+            'Academy.Rec709_D60sim_100nits_dim.a1.0.0']:
+                odtName = odtValues["transformUserName"]
+
+                odtValuesLegal = dict(odtValues)
+                odtValuesLegal["transformUserName"] = "%s - Legal" % odtName
+                odtInfoC["%s - Legal" % odtCTLName] = odtValuesLegal
+
+                odtValuesFull = dict(odtValues)
+                odtValuesFull["transformUserName"] = "%s - Full" % odtName
+                odtInfoC["%s - Full" % odtCTLName] = odtValuesFull
+                
+                del( odtInfoC[odtCTLName] )
+
+    for odtCTLName, odtValues in odtInfoC.iteritems():
+        odtPrefix = odtValues["transformUserNamePrefix"]
+        odtName = odtValues["transformUserName"]
+
+        # For Photoshop
+        for inputspace in ["ACEScc", "ACESproxy"]:
+            args =  ["--iconfig", configPath, "-v", "--inputspace", inputspace ]
+            args += ["--outputspace", "%s" % odtName ]
+            args += ["--description", "%s - %s for %s data" % (odtPrefix, odtName, inputspace) ]
+            args += ["--shaperspace", shaperName, "--shapersize", str(lutResolutionShaper) ] 
+            args += ["--cubesize", str(lutResolution3d) ]
+            args += ["--format", "icc", "%s/photoshop/%s for %s.icc" % (bakedDir, odtName, inputspace) ]
+
+            bakeLUT = process.Process(description="bake a LUT", cmd="ociobakelut", args=args)
+            bakeLUT.execute()    
+
+        # For Flame, Lustre
+        for inputspace in ["ACEScc", "ACESproxy"]:
+            args =  ["--iconfig", configPath, "-v", "--inputspace", inputspace ]
+            args += ["--outputspace", "%s" % odtName ]
+            args += ["--description", "%s - %s for %s data" % (odtPrefix, odtName, inputspace) ]
+            args += ["--shaperspace", shaperName, "--shapersize", str(lutResolutionShaper) ] 
+            args += ["--cubesize", str(lutResolution3d) ]
+
+            fargs = ["--format", "flame", "%s/flame/%s for %s Flame.3dl" % (bakedDir, odtName, inputspace) ]
+            bakeLUT = process.Process(description="bake a LUT", cmd="ociobakelut", args=(args + fargs))
+            bakeLUT.execute()    
+
+            largs = ["--format", "lustre", "%s/lustre/%s for %s Lustre.3dl" % (bakedDir, odtName, inputspace) ]
+            bakeLUT = process.Process(description="bake a LUT", cmd="ociobakelut", args=(args + largs))
+            bakeLUT.execute()
+
+        # For Maya, Houdini
+        for inputspace in ["ACEScg", "ACES2065-1"]:
+            args =  ["--iconfig", configPath, "-v", "--inputspace", inputspace ]
+            args += ["--outputspace", "%s" % odtName ]
+            args += ["--description", "%s - %s for %s data" % (odtPrefix, odtName, inputspace) ]
+            if inputspace == 'ACEScg':
+                linShaperName = "%s AP1" % shaperName 
+            else:
+                linShaperName = shaperName
+            args += ["--shaperspace", linShaperName, "--shapersize", str(lutResolutionShaper) ] 
+            
+            args += ["--cubesize", str(lutResolution3d) ]
+
+            margs = ["--format", "cinespace", "%s/maya/%s for %s Maya.csp" % (bakedDir, odtName, inputspace) ]
+            bakeLUT = process.Process(description="bake a LUT", cmd="ociobakelut", args=(args + margs))
+            bakeLUT.execute()    
+
+            hargs = ["--format", "houdini", "%s/houdini/%s for %s Houdini.lut" % (bakedDir, odtName, inputspace) ]
+            bakeLUT = process.Process(description="bake a LUT", cmd="ociobakelut", args=(args + hargs))
+            bakeLUT.execute()    
+
+
+def createConfigDir(configDir, bakeSecondaryLUTs):
+    dirs = [configDir, "%s/luts" % configDir]
+    if bakeSecondaryLUTs:
+        dirs.extend(["%s/baked" % configDir, 
+            "%s/baked/flame" % configDir, "%s/baked/photoshop" % configDir,
+            "%s/baked/houdini" % configDir, "%s/baked/lustre" % configDir,
+            "%s/baked/maya" % configDir])
+
+    for d in dirs:
+        if not os.path.exists(d):
+            os.mkdir(d)
+
+def getTransformInfo(ctlTransform):
+    fp = open(ctlTransform, 'rb')
+
+    # Read lines
+    lines = fp.readlines()
+
+    # Grab transform ID and User Name
+    transformID = lines[1][3:].split('<')[1].split('>')[1].lstrip().rstrip()
+    #print( transformID )
+    transformUserName = '-'.join(lines[2][3:].split('<')[1].split('>')[1].split('-')[1:]).lstrip().rstrip()
+    transformUserNamePrefix = lines[2][3:].split('<')[1].split('>')[1].split('-')[0].lstrip().rstrip()
+    #print( transformUserName )
+    fp.close()
+
+    return (transformID, transformUserName, transformUserNamePrefix)
+
+# For versions after WGR9
+def getODTInfo(acesCTLReleaseDir):
+    # Credit to Alex Fry for the original approach here
+    odtDir = os.path.join(acesCTLReleaseDir, "odt")
+    allodt = []
+    for dirName, subdirList, fileList in os.walk(odtDir):
+        for fname in fileList:
+            allodt.append((os.path.join(dirName,fname)))
+
+    odtCTLs = [x for x in allodt if ("InvODT" not in x) and (os.path.split(x)[-1][0] != '.')]
+
+    #print odtCTLs
+
+    odts = {}
+
+    for odtCTL in odtCTLs:
+        odtTokens = os.path.split(odtCTL)
+        #print( odtTokens )
+
+        # Handle nested directories
+        odtPathTokens = os.path.split(odtTokens[-2])
+        odtDir = odtPathTokens[-1]
+        while odtPathTokens[-2][-3:] != 'odt':
+            odtPathTokens = os.path.split(odtPathTokens[-2])
+            odtDir = os.path.join(odtPathTokens[-1], odtDir)
+
+        # Build full name
+        #print( "odtDir : %s" % odtDir )
+        transformCTL = odtTokens[-1]
+        #print( transformCTL )
+        odtName = string.join(transformCTL.split('.')[1:-1], '.')
+        #print( odtName )
+
+        # Find id, user name and user name prefix
+        (transformID, transformUserName, transformUserNamePrefix) = getTransformInfo(
+            "%s/odt/%s/%s" % (acesCTLReleaseDir, odtDir, transformCTL) )
+
+        # Find inverse
+        transformCTLInverse = "InvODT.%s.ctl" % odtName
+        if not os.path.exists(os.path.join(odtTokens[-2], transformCTLInverse)):
+            transformCTLInverse = None
+        #print( transformCTLInverse )
+
+        # Add to list of ODTs
+        odts[odtName] = {}
+        odts[odtName]['transformCTL'] = os.path.join(odtDir, transformCTL)
+        if transformCTLInverse != None:
+            odts[odtName]['transformCTLInverse'] = os.path.join(odtDir, transformCTLInverse)
+
+        odts[odtName]['transformID'] = transformID
+        odts[odtName]['transformUserNamePrefix'] = transformUserNamePrefix
+        odts[odtName]['transformUserName'] = transformUserName
+
+        print( "ODT : %s" % odtName )
+        print( "\tTransform ID               : %s" % transformID )
+        print( "\tTransform User Name Prefix : %s" % transformUserNamePrefix )
+        print( "\tTransform User Name        : %s" % transformUserName )
+        print( "\tForward ctl                : %s" % odts[odtName]['transformCTL'])
+        if 'transformCTLInverse' in odts[odtName]:
+            print( "\tInverse ctl                : %s" % odts[odtName]['transformCTLInverse'])
+        else:
+            print( "\tInverse ctl                : %s" % "None" )
+
+    print( "\n" )
+
+    return odts
+
+# For versions after WGR9
+def getLMTInfo(acesCTLReleaseDir):
+    # Credit to Alex Fry for the original approach here
+    lmtDir = os.path.join(acesCTLReleaseDir, "lmt")
+    alllmt = []
+    for dirName, subdirList, fileList in os.walk(lmtDir):
+        for fname in fileList:
+            alllmt.append((os.path.join(dirName,fname)))
+
+    lmtCTLs = [x for x in alllmt if ("InvLMT" not in x) and ("README" not in x) and (os.path.split(x)[-1][0] != '.')]
+
+    #print lmtCTLs
+
+    lmts = {}
+
+    for lmtCTL in lmtCTLs:
+        lmtTokens = os.path.split(lmtCTL)
+        #print( lmtTokens )
+
+        # Handle nested directories
+        lmtPathTokens = os.path.split(lmtTokens[-2])
+        lmtDir = lmtPathTokens[-1]
+        while lmtPathTokens[-2][-3:] != 'ctl':
+            lmtPathTokens = os.path.split(lmtPathTokens[-2])
+            lmtDir = os.path.join(lmtPathTokens[-1], lmtDir)
+
+        # Build full name
+        #print( "lmtDir : %s" % lmtDir )
+        transformCTL = lmtTokens[-1]
+        #print( transformCTL )
+        lmtName = string.join(transformCTL.split('.')[1:-1], '.')
+        #print( lmtName )
+
+        # Find id, user name and user name prefix
+        (transformID, transformUserName, transformUserNamePrefix) = getTransformInfo(
+            "%s/%s/%s" % (acesCTLReleaseDir, lmtDir, transformCTL) )
+
+        # Find inverse
+        transformCTLInverse = "InvLMT.%s.ctl" % lmtName
+        if not os.path.exists(os.path.join(lmtTokens[-2], transformCTLInverse)):
+            transformCTLInverse = None
+        #print( transformCTLInverse )
+
+        # Add to list of LMTs
+        lmts[lmtName] = {}
+        lmts[lmtName]['transformCTL'] = os.path.join(lmtDir, transformCTL)
+        if transformCTLInverse != None:
+            lmts[odtName]['transformCTLInverse'] = os.path.join(lmtDir, transformCTLInverse)
+
+        lmts[lmtName]['transformID'] = transformID
+        lmts[lmtName]['transformUserNamePrefix'] = transformUserNamePrefix
+        lmts[lmtName]['transformUserName'] = transformUserName
+
+        print( "LMT : %s" % lmtName )
+        print( "\tTransform ID               : %s" % transformID )
+        print( "\tTransform User Name Prefix : %s" % transformUserNamePrefix )
+        print( "\tTransform User Name        : %s" % transformUserName )
+        print( "\t Forward ctl : %s" % lmts[lmtName]['transformCTL'])
+        if 'transformCTLInverse' in lmts[lmtName]:
+            print( "\t Inverse ctl : %s" % lmts[lmtName]['transformCTLInverse'])
+        else:
+            print( "\t Inverse ctl : %s" % "None" )
+
+    print( "\n" )
+
+    return lmts
+
+#
+# Create the ACES config
+#
+def createACESConfig(acesCTLReleaseDir, 
+    configDir, 
+    lutResolution1d=4096, 
+    lutResolution3d=64, 
+    bakeSecondaryLUTs=True,
+    cleanup=True):
+
+    # Get ODT names and CTL paths
+    odtInfo = getODTInfo(acesCTLReleaseDir)
+
+    # Get ODT names and CTL paths
+    lmtInfo = getLMTInfo(acesCTLReleaseDir)
+
+    # Create config dir
+    createConfigDir(configDir, bakeSecondaryLUTs)
+
+    # Generate config data and LUTs for different transforms
+    lutDir = "%s/luts" % configDir
+    shaperName = 'outputShaper'
+    configData = generateLUTs(odtInfo, lmtInfo, shaperName, acesCTLReleaseDir, lutDir, lutResolution1d, lutResolution3d, cleanup)
+    
+    # Create the config using the generated LUTs
+    print( "Creating generic config")
+    config = createConfig(configData)
+    print( "\n\n\n" )
+
+    # Write the config to disk
+    writeConfig(config, "%s/config.ocio" % configDir )
+
+    # Create a config that will work well with Nuke using the previously generated LUTs
+    print( "Creating Nuke-specific config")
+    nuke_config = createConfig(configData, nuke=True)
+    print( "\n\n\n" )
+
+    # Write the config to disk
+    writeConfig(nuke_config, "%s/nuke_config.ocio" % configDir )
+
+    # Bake secondary LUTs using the config
+    if bakeSecondaryLUTs:
+        generateBakedLUTs(odtInfo, shaperName, "%s/baked" % configDir, "%s/config.ocio" % configDir, lutResolution1d, lutResolution3d, lutResolution1d)
+
+#
+# Main
+#
+def main():
+    import optparse
+
+    p = optparse.OptionParser(description='An OCIO config generation script',
+                                prog='createACESConfig',
+                                version='createACESConfig 0.1',
+                                usage='%prog [options]')
+    p.add_option('--acesCTLDir', '-a', default=None)
+    p.add_option('--configDir', '-c', default=None)
+    p.add_option('--lutResolution1d', default=4096)
+    p.add_option('--lutResolution3d', default=64)
+    p.add_option('--dontBakeSecondaryLUTs', action="store_true")
+    p.add_option('--keepTempImages', action="store_true")
+
+    options, arguments = p.parse_args()
+
+    #
+    # Get options
+    # 
+    acesCTLDir = options.acesCTLDir
+    configDir  = options.configDir
+    lutResolution1d  = int(options.lutResolution1d)
+    lutResolution3d  = int(options.lutResolution3d)
+    bakeSecondaryLUTs  = not(options.dontBakeSecondaryLUTs)
+    cleanupTempImages  = not(options.keepTempImages)
+
+    try:
+        argsStart = sys.argv.index('--') + 1
+        args = sys.argv[argsStart:]
+    except:
+        argsStart = len(sys.argv)+1
+        args = []
+
+    print( "command line : \n%s\n" % " ".join(sys.argv) )
+
+    if configDir == None:
+        print( "process: No ACES CTL directory specified" )
+        return
+    #
+    # Generate the configuration
+    #
+    createACESConfig(acesCTLDir, configDir, lutResolution1d, lutResolution3d, bakeSecondaryLUTs, cleanupTempImages)
+# main
+
+if __name__ == '__main__':
+    main()
diff --git a/aces_1.0.0/python/process.py b/aces_1.0.0/python/process.py
new file mode 100755 (executable)
index 0000000..17347ee
--- /dev/null
@@ -0,0 +1,474 @@
+#!/usr/bin/python2.6\r
+\r
+'''A process wrapper class that maintains the text output and execution status of a process\r
+or a list of other process wrappers which carry such data.'''\r
+\r
+import os\r
+import sys\r
+import traceback\r
+\r
+def readText(textFile):\r
+    if( textFile != "" ):\r
+        fp = open(textFile, 'rb')\r
+        # Create a text/plain message\r
+        text = (fp.read())\r
+        fp.close()\r
+    return text\r
+# readText\r
+\r
+def writeText(text, textFile):\r
+    if( textFile != "" ):\r
+        fp = open(textFile, 'wb')\r
+        # Create a text/plain message\r
+        fp.write(text)\r
+        fp.close()\r
+    return text\r
+# readText\r
+\r
+class Process:\r
+    "A process with logged output"\r
+\r
+    def __init__(self, description=None, cmd=None, args=[], cwd=None, env=None, batchWrapper=False):\r
+        "Initialize the standard class variables"\r
+        self.cmd = cmd\r
+        if not description:\r
+            self.description = cmd\r
+        else:\r
+            self.description = description\r
+        self.status = None\r
+        self.args = args\r
+        self.start = None\r
+        self.end = None\r
+        self.log = []\r
+        self.echo = True\r
+        self.cwd = cwd\r
+        self.env = env\r
+        self.batchWrapper = batchWrapper\r
+        self.processKeys = []\r
+    # __init__\r
+\r
+    def getElapsedSeconds(self):\r
+        import math\r
+        if self.end and self.start:\r
+            delta = (self.end - self.start)\r
+            formatted = "%s.%s" % (delta.days * 86400 + delta.seconds, int(math.floor(delta.microseconds/1e3)))\r
+        else:\r
+            formatted = None\r
+        return formatted\r
+    # getElapsedtime\r
+\r
+    def writeKey(self, writeDict, key=None, value=None, startStop=None):\r
+        "Write a key, value pair in a supported format"\r
+        if key != None and (value != None or startStop != None):\r
+            indent = '\t'*writeDict['indentationLevel']\r
+            if writeDict['format'] == 'xml':\r
+                if startStop == 'start':\r
+                    writeDict['logHandle'].write( "%s<%s>\n" % (indent, key) )\r
+                elif startStop == 'stop':\r
+                    writeDict['logHandle'].write( "%s</%s>\n" % (indent, key) )\r
+                else:\r
+                    writeDict['logHandle'].write( "%s<%s>%s</%s>\n" % (indent, key, value, key) )\r
+            else: # writeDict['format'] == 'txt':\r
+                writeDict['logHandle'].write( "%s%40s : %s\n" % (indent, key, value) )\r
+\r
+    def writeLogHeader(self, writeDict):\r
+        import platform\r
+\r
+        # Retrieve operating environment information\r
+        user = None\r
+        try:\r
+            user = os.getlogin()\r
+        except:\r
+            try:\r
+                user = os.getenv("USERNAME")\r
+                if user == None:\r
+                    user = os.getenv("USER")\r
+            except:\r
+                user = "unknown_user"\r
+        try:\r
+            (sysname, nodename, release, version, machine, processor) = platform.uname()\r
+        except:\r
+            (sysname, nodename, release, version, machine, processor) = ("unknown_sysname", "unknown_nodename", "unknown_release", "unknown_version", "unknown_machine", "unknown_processor")\r
+        try:\r
+            hostname = platform.node()\r
+        except:\r
+            hostname = "unknown_hostname"\r
+\r
+        self.writeKey(writeDict, 'process', None, 'start' )\r
+        writeDict['indentationLevel'] += 1\r
+\r
+        self.writeKey(writeDict, 'description', self.description )\r
+        self.writeKey(writeDict, 'cmd', self.cmd )\r
+        if self.args: self.writeKey(writeDict, 'args', ' '.join(self.args) )\r
+        self.writeKey(writeDict, 'start', self.start )\r
+        self.writeKey(writeDict, 'end', self.end )\r
+        self.writeKey(writeDict, 'elapsed', self.getElapsedSeconds() )\r
+\r
+        self.writeKey(writeDict, 'user', user )\r
+        self.writeKey(writeDict, 'sysname', sysname )\r
+        self.writeKey(writeDict, 'nodename', nodename )\r
+        self.writeKey(writeDict, 'release', release )\r
+        self.writeKey(writeDict, 'version', version )\r
+        self.writeKey(writeDict, 'machine', machine )\r
+        self.writeKey(writeDict, 'processor', processor )\r
+\r
+        if len(self.processKeys) > 0:\r
+            self.writeKey(writeDict, 'processKeys', None, 'start' )\r
+            for pair in self.processKeys:\r
+                (key, value) = pair\r
+                writeDict['indentationLevel'] += 1\r
+                self.writeKey(writeDict, key, value )\r
+                writeDict['indentationLevel'] -= 1\r
+            self.writeKey(writeDict, 'processKeys', None, 'stop' )\r
+\r
+        self.writeKey(writeDict, 'status', self.status )\r
+    # writeLogHeader\r
+\r
+    def writeLogFooter(self, writeDict):\r
+        writeDict['indentationLevel'] -= 1\r
+        self.writeKey(writeDict, 'process', None, 'stop' )\r
+    # writeLogFooter\r
+\r
+    def writeLog(self, logHandle=sys.stdout, indentationLevel=0,format='xml'):\r
+        "Write logging information to the specified handle"\r
+        \r
+        writeDict = {}\r
+        writeDict['logHandle'] = logHandle\r
+        writeDict['indentationLevel'] = indentationLevel\r
+        writeDict['format'] = format\r
+        \r
+        if logHandle:\r
+            self.writeLogHeader(writeDict)\r
+            \r
+            if self.log:\r
+                self.writeKey(writeDict, 'output', None, 'start' )\r
+                if format == 'xml':\r
+                    logHandle.write( "<![CDATA[\n" )\r
+                for line in self.log:\r
+                    logHandle.write( '%s%s\n' % ("", line) )\r
+                if format == 'xml':\r
+                    logHandle.write( "]]>\n" )\r
+                self.writeKey(writeDict, 'output', None, 'stop' )\r
+\r
+            self.writeLogFooter(writeDict)\r
+    # writeLog\r
+\r
+    def writeLogToDisk(self, logFilename=None, format='xml', header=None):\r
+        if logFilename: \r
+            try:\r
+                # This also doesn't seem like the best structure...\r
+                # 3.1\r
+                try:\r
+                    logHandle = open( logFilename, mode='wt', encoding="utf-8")\r
+                # 2.6\r
+                except:\r
+                    logHandle = open( logFilename, mode='wt')\r
+            except:\r
+                print( "Couldn't open log : %s" % logFilename )\r
+                logHandle = None\r
+\r
+        if logHandle:\r
+            if header:\r
+                if format == 'xml':\r
+                    logHandle.write( "<![CDATA[\n" )\r
+                logHandle.write( header )\r
+                if format == 'xml':\r
+                    logHandle.write( "]]>\n" )\r
+            self.writeLog(logHandle)\r
+            logHandle.close()\r
+    # writeLogToDisk\r
+\r
+    def logLine(self, line):\r
+        "Add a line of text to the log"\r
+        self.log.append( line.rstrip() )\r
+        if self.echo:\r
+            print( "%s" % line.rstrip() )\r
+    # logLine\r
+\r
+    def execute(self):\r
+        "Execute this process"\r
+        import re\r
+        import datetime\r
+        import traceback\r
+        \r
+        try:\r
+            import subprocess as sp\r
+        except:\r
+            sp = None\r
+\r
+        self.start = datetime.datetime.now()\r
+\r
+        cmdargs = [self.cmd]\r
+        cmdargs.extend(self.args)\r
+        \r
+        if self.echo:\r
+            if sp:\r
+                print( "\n%s : %s\n" % (self.__class__, sp.list2cmdline(cmdargs)) )\r
+            else:\r
+                print( "\n%s : %s\n" % (self.__class__, " ".join(cmdargs)) )\r
+\r
+        # intialize a few variables that may or may not be set later\r
+        process = None\r
+        tmpWrapper = None\r
+        stdout = None\r
+        stdin = None\r
+        parentenv = os.environ\r
+        parentcwd = os.getcwd()\r
+\r
+        try:\r
+            # Using subprocess\r
+            if sp:\r
+                if self.batchWrapper:\r
+                    cmd = " ".join(cmdargs)\r
+                    tmpWrapper = os.path.join(self.cwd, "process.bat")\r
+                    writeText(cmd, tmpWrapper)\r
+                    print( "%s : Running process through wrapper %s\n" % (self.__class__, tmpWrapper) )\r
+                    process = sp.Popen([tmpWrapper], stdout=sp.PIPE, stderr=sp.STDOUT, \r
+                        cwd=self.cwd, env=self.env)\r
+                else:\r
+                    process = sp.Popen(cmdargs, stdout=sp.PIPE, stderr=sp.STDOUT, \r
+                        cwd=self.cwd, env=self.env)\r
+\r
+            # using os.popen4\r
+            else:\r
+                if self.env:\r
+                    os.environ = self.env\r
+                if self.cwd:\r
+                    os.chdir( self.cwd )\r
+                \r
+                stdin, stdout = os.popen4( cmdargs, 'r')\r
+        except:\r
+            print( "Couldn't execute command : %s" % cmdargs[0] )\r
+            traceback.print_exc()\r
+\r
+        # Using subprocess\r
+        if sp:\r
+            if process != None:\r
+                #pid = process.pid\r
+                #log.logLine( "process id %s\n" % pid )\r
+\r
+                try:\r
+                    # This is more proper python, and resolves some issues with a process ending before all\r
+                    #  of its output has been processed, but it also seems to stall when the read buffer\r
+                    #  is near or over it's limit. this happens relatively frequently with processes\r
+                    #  that generate lots of print statements.\r
+                    #\r
+                    for line in process.stdout:\r
+                        self.logLine(line)\r
+                    #\r
+                    # So we go with the, um, uglier  option below\r
+\r
+                    # This is now used to ensure that the process has finished\r
+                    line = ""\r
+                    while line != None and process.poll() == None:\r
+                        try:\r
+                            line = process.stdout.readline()\r
+                        except:\r
+                            break\r
+                        # 3.1\r
+                        try:\r
+                            self.logLine( str(line, encoding="utf-8") )\r
+                        # 2.6\r
+                        except:\r
+                            self.logLine( line )\r
+                except:\r
+                    self.logLine( "Logging error : %s" % sys.exc_info()[0] )\r
+\r
+                self.status = process.returncode\r
+                \r
+                if self.batchWrapper and tmpWrapper:\r
+                    try:\r
+                        os.remove(tmpWrapper)\r
+                    except:\r
+                        print( "Couldn't remove temp wrapper : %s" % tmpWrapper )\r
+                        traceback.print_exc()\r
+\r
+        # Using os.popen4\r
+        else:\r
+            exitCode = -1\r
+            try:\r
+                #print( "reading stdout lines" )\r
+                stdoutLines = stdout.readlines()\r
+                exitCode = stdout.close()\r
+\r
+                stdout.close()\r
+                stdin.close()\r
+\r
+                if self.env:\r
+                    os.environ = parentenv\r
+                if self.cwd:\r
+                    os.chdir( parentcwd )\r
+                \r
+                if len( stdoutLines ) > 0:\r
+                    for line in stdoutLines:\r
+                        self.logLine(line)\r
+\r
+                if not exitCode:\r
+                    exitCode = 0\r
+            except:\r
+                self.logLine( "Logging error : %s" % sys.exc_info()[0] )\r
+\r
+            self.status = exitCode\r
+            \r
+        self.end = datetime.datetime.now()\r
+    #execute\r
+# Process\r
+\r
+class ProcessList(Process):\r
+    "A list of processes with logged output"\r
+\r
+    def __init__(self, description, blocking=True, cwd=None, env=None):\r
+        Process.__init__(self, description, None, None, cwd, env)\r
+        "Initialize the standard class variables"\r
+        self.processes = []\r
+        self.blocking = blocking\r
+    # __init__\r
+\r
+    def generateReport(self, writeDict):\r
+        "Generate a log based on the success of the child processes"\r
+        if self.processes:\r
+            _status = True\r
+            indent = '\t'*(writeDict['indentationLevel']+1)\r
+            \r
+            self.log = []\r
+            \r
+            for child in self.processes:\r
+                if isinstance(child, ProcessList):\r
+                    child.generateReport(writeDict)\r
+                \r
+                childResult = ""\r
+                key = child.description\r
+                value = child.status\r
+                if writeDict['format'] == 'xml':\r
+                    childResult = ( "%s<result description=\"%s\">%s</result>" % (indent, key, value) )\r
+                else: # writeDict['format'] == 'txt':\r
+                    childResult = ( "%s%40s : %s" % (indent, key, value) )\r
+                self.log.append( childResult )\r
+                \r
+                if child.status != 0:\r
+                    _status = False\r
+            if not _status:\r
+                self.status = -1\r
+            else:\r
+                self.status = 0\r
+        else:\r
+            self.log = ["No child processes available to generate a report"]\r
+            self.status = -1\r
+\r
+    def writeLogHeader(self, writeDict):\r
+        self.writeKey(writeDict, 'processList', None, 'start' )\r
+        writeDict['indentationLevel'] += 1\r
+\r
+        self.writeKey(writeDict, 'description', self.description )\r
+        self.writeKey(writeDict, 'start', self.start )\r
+        self.writeKey(writeDict, 'end', self.end )\r
+        self.writeKey(writeDict, 'elapsed', self.getElapsedSeconds() )\r
+\r
+        self.generateReport(writeDict)\r
+\r
+        self.writeKey(writeDict, 'status', self.status )\r
+    # writeLogHeader\r
+\r
+    def writeLogFooter(self, writeDict):\r
+        writeDict['indentationLevel'] -= 1\r
+        self.writeKey(writeDict, 'processList', None, 'stop' )\r
+    # writeLogFooter\r
+\r
+    def writeLog(self, logHandle=sys.stdout, indentationLevel=0,format='xml'):\r
+        "Write logging information to the specified handle"\r
+        \r
+        writeDict = {}\r
+        writeDict['logHandle'] = logHandle\r
+        writeDict['indentationLevel'] = indentationLevel\r
+        writeDict['format'] = format\r
+        \r
+        if logHandle:\r
+            self.writeLogHeader(writeDict)\r
+            \r
+            if self.log:\r
+                self.writeKey(writeDict, 'output', None, 'start' )\r
+                for line in self.log:\r
+                    logHandle.write( '%s%s\n' % ("", line) )\r
+                self.writeKey(writeDict, 'output', None, 'stop' )\r
+\r
+            if self.processes:\r
+                self.writeKey(writeDict, 'processes', None, 'start' )\r
+                for child in self.processes:\r
+                    child.writeLog( logHandle, indentationLevel + 1, format )\r
+                self.writeKey(writeDict, 'processes', None, 'stop' )\r
+\r
+            self.writeLogFooter(writeDict)\r
+    # writeLog\r
+\r
+    def execute(self):\r
+        "Execute this list of processes"\r
+        import datetime\r
+\r
+        self.start = datetime.datetime.now()\r
+\r
+        self.status = 0\r
+        if self.processes:\r
+            for child in self.processes:\r
+                if child:\r
+                    try:\r
+                        child.execute()\r
+                    except:\r
+                        print( "%s : caught exception in child class %s" % (self.__class__, child.__class__) )\r
+                        traceback.print_exc()\r
+                        child.status = -1\r
+\r
+                    if self.blocking and child.status != 0:\r
+                        print( "%s : child class %s finished with an error" % (self.__class__, child.__class__) )\r
+                        self.status = -1\r
+                        break\r
+\r
+        self.end = datetime.datetime.now()\r
+    # execute\r
+# ProcessList\r
+\r
+def main():\r
+    import optparse\r
+\r
+    p = optparse.OptionParser(description='A process logging script',\r
+                                prog='process',\r
+                                version='process 0.1',\r
+                                usage='%prog [options] [options for the logged process]')\r
+    p.add_option('--cmd', '-c', default=None)\r
+    p.add_option('--log', '-l', default=None)\r
+\r
+    options, arguments = p.parse_args()\r
+\r
+    #\r
+    # Get options\r
+    # \r
+    cmd = options.cmd\r
+    logFilename = options.log\r
+\r
+    try:\r
+        argsStart = sys.argv.index('--') + 1\r
+        args = sys.argv[argsStart:]\r
+    except:\r
+        argsStart = len(sys.argv)+1\r
+        args = []\r
+\r
+    if cmd == None:\r
+        print( "process: No command specified" )\r
\r
+    #\r
+    # Test regular logging\r
+    #\r
+    process = Process(description="a process",cmd=cmd, args=args)\r
+\r
+    #\r
+    # Test report generation and writing a log\r
+    #\r
+    processList = ProcessList("a process list")\r
+    processList.processes.append( process )\r
+    processList.echo = True\r
+    processList.execute()\r
+    \r
+    processList.writeLogToDisk(logFilename)\r
+# main\r
+\r
+if __name__ == '__main__':\r
+    main()\r